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
|
qmldesignerutils_global.h
|
||||||
version.cpp version.h
|
version.cpp version.h
|
||||||
maputils.h
|
maputils.h
|
||||||
stringutils.h
|
stringutils.cpp stringutils.h
|
||||||
)
|
)
|
||||||
|
|
||||||
extend_qtc_library(QmlDesignerUtils
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include "qmldesignerutils_global.h"
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringView>
|
#include <QStringView>
|
||||||
|
|
||||||
@@ -11,107 +13,9 @@
|
|||||||
|
|
||||||
namespace QmlDesigner::StringUtils {
|
namespace QmlDesigner::StringUtils {
|
||||||
|
|
||||||
inline QString escape(QStringView text)
|
QMLDESIGNERUTILS_EXPORT QString escape(QStringView text);
|
||||||
{
|
|
||||||
using namespace Qt::StringLiterals;
|
|
||||||
|
|
||||||
if (text.size() == 6 && text.startsWith(u"\\u")) //Do not double escape unicode chars
|
QMLDESIGNERUTILS_EXPORT QString deescape(QStringView text);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
concept is_object = std::is_object_v<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()}};
|
return {{text.begin(), std::prev(splitPoint)}, {splitPoint, text.end()}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QMLDESIGNERUTILS_EXPORT QStringView::iterator find_comment_end(QStringView text);
|
||||||
|
|
||||||
} // namespace QmlDesigner::StringUtils
|
} // namespace QmlDesigner::StringUtils
|
||||||
|
@@ -8,6 +8,7 @@ namespace {
|
|||||||
|
|
||||||
using namespace Qt::StringLiterals;
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
using QmlDesigner::StringUtils::find_comment_end;
|
||||||
using QmlDesigner::StringUtils::split_last;
|
using QmlDesigner::StringUtils::split_last;
|
||||||
|
|
||||||
TEST(StringUtils_split_last, leaf_is_empty_for_empty_input)
|
TEST(StringUtils_split_last, leaf_is_empty_for_empty_input)
|
||||||
@@ -222,4 +223,63 @@ TEST_P(escaping, skip_unicode)
|
|||||||
ASSERT_THAT(converted, input);
|
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
|
} // namespace
|
||||||
|
Reference in New Issue
Block a user