forked from qt-creator/qt-creator
QmlDesigner: Add string utils function
Add utilities for string matching and comment detection in QStringView - Implemented `find_comment_end()` to locate the end of a comment block or line. - Move complex functions to .cpp file Change-Id: Id544905d9486b6c4e7cddc91213956830937d8ae Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -17,7 +17,7 @@ add_qtc_library(QmlDesignerUtils STATIC
|
||||
qmldesignerutils_global.h
|
||||
version.cpp version.h
|
||||
maputils.h
|
||||
stringutils.h
|
||||
stringutils.cpp stringutils.h
|
||||
)
|
||||
|
||||
extend_qtc_library(QmlDesignerUtils
|
||||
|
154
src/plugins/qmldesigner/libs/qmldesignerutils/stringutils.cpp
Normal file
154
src/plugins/qmldesigner/libs/qmldesignerutils/stringutils.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "stringutils.h"
|
||||
|
||||
namespace QmlDesigner::StringUtils {
|
||||
|
||||
QString escape(QStringView text)
|
||||
{
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
if (text.size() == 6 && text.startsWith(u"\\u")) //Do not double escape unicode chars
|
||||
return text.toString();
|
||||
|
||||
QString escapedText;
|
||||
escapedText.reserve(text.size() * 2);
|
||||
|
||||
const auto end = text.end();
|
||||
auto current = text.begin();
|
||||
QStringView pattern = u"\\\"\t\r\n";
|
||||
while (current != end) {
|
||||
auto found = std::ranges::find_first_of(current, end, pattern.begin(), pattern.end());
|
||||
escapedText.append(QStringView{current, found});
|
||||
|
||||
if (found == end)
|
||||
break;
|
||||
|
||||
QChar c = *found;
|
||||
switch (c.unicode()) {
|
||||
case u'\\':
|
||||
escapedText.append(u"\\\\");
|
||||
break;
|
||||
case u'\"':
|
||||
escapedText.append(u"\\\"");
|
||||
break;
|
||||
case u'\t':
|
||||
escapedText.append(u"\\t");
|
||||
break;
|
||||
case u'\r':
|
||||
escapedText.append(u"\\r");
|
||||
break;
|
||||
case u'\n':
|
||||
escapedText.append(u"\\n");
|
||||
break;
|
||||
}
|
||||
|
||||
current = std::next(found);
|
||||
}
|
||||
|
||||
return escapedText;
|
||||
}
|
||||
|
||||
QString deescape(QStringView text)
|
||||
{
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
if (text.isEmpty() || (text.size() == 6 && text.startsWith(u"\\u"))) //Ignore unicode chars
|
||||
return text.toString();
|
||||
|
||||
QString deescapedText;
|
||||
deescapedText.reserve(text.size());
|
||||
|
||||
const auto end = text.end();
|
||||
auto current = text.begin();
|
||||
while (current != end) {
|
||||
auto found = std::ranges::find(current, end, u'\\');
|
||||
deescapedText.append(QStringView{current, found});
|
||||
|
||||
if (found == end)
|
||||
break;
|
||||
|
||||
current = std::next(found);
|
||||
|
||||
if (current == end) {
|
||||
deescapedText.append(u'\\');
|
||||
break;
|
||||
}
|
||||
|
||||
QChar c = *current;
|
||||
switch (c.unicode()) {
|
||||
case u'\\':
|
||||
deescapedText.append(u'\\');
|
||||
current = std::next(current);
|
||||
break;
|
||||
case u'\"':
|
||||
deescapedText.append(u'\"');
|
||||
current = std::next(current);
|
||||
break;
|
||||
case u't':
|
||||
deescapedText.append(u'\t');
|
||||
current = std::next(current);
|
||||
break;
|
||||
case u'r':
|
||||
deescapedText.append(u'\r');
|
||||
current = std::next(current);
|
||||
break;
|
||||
case u'n':
|
||||
deescapedText.append(u'\n');
|
||||
current = std::next(current);
|
||||
break;
|
||||
default:
|
||||
deescapedText.append(u'\\');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return deescapedText;
|
||||
}
|
||||
|
||||
namespace {
|
||||
enum class Comment { No, Line, Block };
|
||||
|
||||
Comment commentKind(QStringView text)
|
||||
{
|
||||
if (text.size() < 2)
|
||||
return Comment::No;
|
||||
|
||||
if (text.startsWith(u"//"))
|
||||
return Comment::Line;
|
||||
|
||||
if (text.startsWith(u"/*"))
|
||||
return Comment::Block;
|
||||
|
||||
return Comment::No;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
QStringView::iterator find_comment_end(QStringView text)
|
||||
{
|
||||
auto current = text.begin();
|
||||
const auto end = text.end();
|
||||
|
||||
while (current != end) {
|
||||
text = {current, text.end()};
|
||||
switch (commentKind(text)) {
|
||||
case Comment::No:
|
||||
return current;
|
||||
case Comment::Line:
|
||||
current = std::ranges::find(text, u'\n');
|
||||
break;
|
||||
case Comment::Block:
|
||||
current = std::ranges::search(text, QStringView{u"*/"}).end();
|
||||
current = std::ranges::find(current, end, u'\n');
|
||||
break;
|
||||
}
|
||||
|
||||
if (current != end)
|
||||
current = std::next(current);
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
} // namespace QmlDesigner::StringUtils
|
@@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "qmldesignerutils_global.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QStringView>
|
||||
|
||||
@@ -11,107 +13,9 @@
|
||||
|
||||
namespace QmlDesigner::StringUtils {
|
||||
|
||||
inline QString escape(QStringView text)
|
||||
{
|
||||
using namespace Qt::StringLiterals;
|
||||
QMLDESIGNERUTILS_EXPORT QString escape(QStringView text);
|
||||
|
||||
if (text.size() == 6 && text.startsWith(u"\\u")) //Do not double escape unicode chars
|
||||
return text.toString();
|
||||
|
||||
QString escapedText;
|
||||
escapedText.reserve(text.size() * 2);
|
||||
|
||||
const auto end = text.end();
|
||||
auto current = text.begin();
|
||||
QStringView pattern = u"\\\"\t\r\n";
|
||||
while (current != end) {
|
||||
auto found = std::ranges::find_first_of(current, end, pattern.begin(), pattern.end());
|
||||
escapedText.append(QStringView{current, found});
|
||||
|
||||
if (found == end)
|
||||
break;
|
||||
|
||||
QChar c = *found;
|
||||
switch (c.unicode()) {
|
||||
case u'\\':
|
||||
escapedText.append(u"\\\\");
|
||||
break;
|
||||
case u'\"':
|
||||
escapedText.append(u"\\\"");
|
||||
break;
|
||||
case u'\t':
|
||||
escapedText.append(u"\\t");
|
||||
break;
|
||||
case u'\r':
|
||||
escapedText.append(u"\\r");
|
||||
break;
|
||||
case u'\n':
|
||||
escapedText.append(u"\\n");
|
||||
break;
|
||||
}
|
||||
|
||||
current = std::next(found);
|
||||
}
|
||||
|
||||
return escapedText;
|
||||
}
|
||||
|
||||
inline QString deescape(QStringView text)
|
||||
{
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
if (text.isEmpty() || (text.size() == 6 && text.startsWith(u"\\u"))) //Ignore unicode chars
|
||||
return text.toString();
|
||||
|
||||
QString deescapedText;
|
||||
deescapedText.reserve(text.size());
|
||||
|
||||
const auto end = text.end();
|
||||
auto current = text.begin();
|
||||
while (current != end) {
|
||||
auto found = std::ranges::find(current, end, u'\\');
|
||||
deescapedText.append(QStringView{current, found});
|
||||
|
||||
if (found == end)
|
||||
break;
|
||||
|
||||
current = std::next(found);
|
||||
|
||||
if (current == end) {
|
||||
deescapedText.append(u'\\');
|
||||
break;
|
||||
}
|
||||
|
||||
QChar c = *current;
|
||||
switch (c.unicode()) {
|
||||
case u'\\':
|
||||
deescapedText.append(u'\\');
|
||||
current = std::next(current);
|
||||
break;
|
||||
case u'\"':
|
||||
deescapedText.append(u'\"');
|
||||
current = std::next(current);
|
||||
break;
|
||||
case u't':
|
||||
deescapedText.append(u'\t');
|
||||
current = std::next(current);
|
||||
break;
|
||||
case u'r':
|
||||
deescapedText.append(u'\r');
|
||||
current = std::next(current);
|
||||
break;
|
||||
case u'n':
|
||||
deescapedText.append(u'\n');
|
||||
current = std::next(current);
|
||||
break;
|
||||
default:
|
||||
deescapedText.append(u'\\');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return deescapedText;
|
||||
}
|
||||
QMLDESIGNERUTILS_EXPORT QString deescape(QStringView text);
|
||||
|
||||
template<typename T>
|
||||
concept is_object = std::is_object_v<T>;
|
||||
@@ -128,4 +32,6 @@ inline std::pair<QStringView, QStringView> split_last(QStringView text, QChar c)
|
||||
return {{text.begin(), std::prev(splitPoint)}, {splitPoint, text.end()}};
|
||||
}
|
||||
|
||||
QMLDESIGNERUTILS_EXPORT QStringView::iterator find_comment_end(QStringView text);
|
||||
|
||||
} // namespace QmlDesigner::StringUtils
|
||||
|
@@ -8,6 +8,7 @@ namespace {
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
using QmlDesigner::StringUtils::find_comment_end;
|
||||
using QmlDesigner::StringUtils::split_last;
|
||||
|
||||
TEST(StringUtils_split_last, leaf_is_empty_for_empty_input)
|
||||
@@ -222,4 +223,63 @@ TEST_P(escaping, skip_unicode)
|
||||
ASSERT_THAT(converted, input);
|
||||
}
|
||||
|
||||
TEST(StringUtils, find_comment_end_empty)
|
||||
{
|
||||
QStringView input{};
|
||||
|
||||
auto found = find_comment_end(input);
|
||||
|
||||
ASSERT_THAT(found, input.end());
|
||||
}
|
||||
|
||||
TEST(StringUtils, find_comment_end_line_comment)
|
||||
{
|
||||
QStringView input{u"//foo\n//bar\nbar"};
|
||||
|
||||
auto found = find_comment_end(input);
|
||||
|
||||
QStringView text{found, input.end()};
|
||||
ASSERT_THAT(text, u"bar");
|
||||
}
|
||||
|
||||
TEST(StringUtils, find_comment_end_block_comment)
|
||||
{
|
||||
QStringView input{u"/*foo\n//bar*/foo\nbar"};
|
||||
|
||||
auto found = find_comment_end(input);
|
||||
|
||||
QStringView text{found, input.end()};
|
||||
ASSERT_THAT(text, u"bar");
|
||||
}
|
||||
|
||||
TEST(StringUtils, find_comment_end_block_and_line_comment)
|
||||
{
|
||||
QStringView input{u"//foo\n/*bar*/foo\nbar"};
|
||||
|
||||
auto found = find_comment_end(input);
|
||||
|
||||
QStringView text{found, input.end()};
|
||||
ASSERT_THAT(text, u"bar");
|
||||
}
|
||||
|
||||
TEST(StringUtils, find_comment_end_line_comment_has_no_newline)
|
||||
{
|
||||
QStringView input{u"//foobar"};
|
||||
|
||||
auto found = find_comment_end(input);
|
||||
|
||||
QStringView text{found, input.end()};
|
||||
ASSERT_THAT(text, input.end());
|
||||
}
|
||||
|
||||
TEST(StringUtils, find_comment_end_block_comment_has_no_newline)
|
||||
{
|
||||
QStringView input{u"//foo\n/*bar*/foo"};
|
||||
|
||||
auto found = find_comment_end(input);
|
||||
|
||||
QStringView text{found, input.end()};
|
||||
ASSERT_THAT(text, input.end());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
Reference in New Issue
Block a user