forked from qt-creator/qt-creator
QmlDesigner: Refactor escape and deescape functions
- Replaced QString::replace with manual text handling to avoid multiple loops. - Changed parameter type from QString to QStringView for better performance. - Improved handling of special characters and reserved memory for efficiency. Change-Id: I984df7b555f6e0fc115575e4e7e0606b55703520 Reviewed-by: Burak Hancerli <burak.hancerli@qt.io> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -11,40 +11,106 @@
|
||||
|
||||
namespace QmlDesigner::StringUtils {
|
||||
|
||||
inline QString escape(const QString &value)
|
||||
inline QString escape(QStringView text)
|
||||
{
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
if (value.length() == 6 && value.startsWith("\\u")) //Do not double escape unicode chars
|
||||
return value;
|
||||
if (text.size() == 6 && text.startsWith(u"\\u")) //Do not double escape unicode chars
|
||||
return text.toString();
|
||||
|
||||
QString result = value;
|
||||
QString escapedText;
|
||||
escapedText.reserve(text.size() * 2);
|
||||
|
||||
result.replace("\\"_L1, "\\\\"_L1);
|
||||
result.replace("\""_L1, "\\\""_L1);
|
||||
result.replace("\t"_L1, "\\t"_L1);
|
||||
result.replace("\r"_L1, "\\r"_L1);
|
||||
result.replace("\n"_L1, "\\n"_L1);
|
||||
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});
|
||||
|
||||
return result;
|
||||
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(const QString &value)
|
||||
inline QString deescape(QStringView text)
|
||||
{
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
if (value.length() == 6 && value.startsWith("\\u")) //Ignore unicode chars
|
||||
return value;
|
||||
if (text.isEmpty() || (text.size() == 6 && text.startsWith(u"\\u"))) //Ignore unicode chars
|
||||
return text.toString();
|
||||
|
||||
QString result = value;
|
||||
QString deescapedText;
|
||||
deescapedText.reserve(text.size());
|
||||
|
||||
result.replace("\\\\"_L1, "\\"_L1);
|
||||
result.replace("\\\""_L1, "\""_L1);
|
||||
result.replace("\\t"_L1, "\t"_L1);
|
||||
result.replace("\\r"_L1, "\r"_L1);
|
||||
result.replace("\\n"_L1, "\n"_L1);
|
||||
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});
|
||||
|
||||
return result;
|
||||
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>
|
||||
|
@@ -6,6 +6,8 @@
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
using QmlDesigner::StringUtils::split_last;
|
||||
|
||||
TEST(StringUtils_split_last, leaf_is_empty_for_empty_input)
|
||||
@@ -79,4 +81,138 @@ TEST(StringUtils_split_last, no_steam_for_not_dot)
|
||||
|
||||
ASSERT_THAT(steam, IsEmpty());
|
||||
}
|
||||
|
||||
using ConvertFunction = QString (*)(QStringView);
|
||||
|
||||
struct EscapeParameters
|
||||
{
|
||||
QString input;
|
||||
QString output;
|
||||
std::string name;
|
||||
ConvertFunction convert;
|
||||
};
|
||||
|
||||
class escaping : public testing::TestWithParam<EscapeParameters>
|
||||
{
|
||||
public:
|
||||
escaping()
|
||||
: inputTerm{GetParam().input}
|
||||
, outputTerm{GetParam().output}
|
||||
, name{GetParam().name}
|
||||
, convert{GetParam().convert}
|
||||
{}
|
||||
|
||||
public:
|
||||
const QString &inputTerm;
|
||||
const QString &outputTerm;
|
||||
const std::string &name;
|
||||
ConvertFunction convert;
|
||||
};
|
||||
|
||||
auto excape_printer = [](const testing::TestParamInfo<EscapeParameters> &info) {
|
||||
return info.param.name;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
StringUtils,
|
||||
escaping,
|
||||
testing::Values(
|
||||
EscapeParameters(u"\\"_s, u"\\\\"_s, "escape_backslash", QmlDesigner::StringUtils::escape),
|
||||
EscapeParameters(u"\""_s, u"\\\""_s, "escape_quote", QmlDesigner::StringUtils::escape),
|
||||
EscapeParameters(u"\t"_s, u"\\t"_s, "escape_tab", QmlDesigner::StringUtils::escape),
|
||||
EscapeParameters(u"\n"_s, u"\\n"_s, "escape_new_line", QmlDesigner::StringUtils::escape),
|
||||
EscapeParameters(u"\r"_s, u"\\r"_s, "escape_carriage_return", QmlDesigner::StringUtils::escape),
|
||||
EscapeParameters(u"\\"_s, u"\\"_s, "deescape_backslash", QmlDesigner::StringUtils::deescape),
|
||||
EscapeParameters(u"\\\\"_s, u"\\"_s, "deescape_double_backslash", QmlDesigner::StringUtils::deescape),
|
||||
EscapeParameters(u"\\\""_s, u"\""_s, "deescape_quote", QmlDesigner::StringUtils::deescape),
|
||||
EscapeParameters(u"\\t"_s, u"\t"_s, "deescape_tab", QmlDesigner::StringUtils::deescape),
|
||||
EscapeParameters(u"\\n"_s, u"\n"_s, "deescape_new_line", QmlDesigner::StringUtils::deescape),
|
||||
EscapeParameters(
|
||||
u"\\r"_s, u"\r"_s, "deescape_carriage_return", QmlDesigner::StringUtils::deescape)),
|
||||
excape_printer);
|
||||
|
||||
TEST_P(escaping, begin)
|
||||
{
|
||||
QString input = inputTerm + "foo";
|
||||
|
||||
auto converted = convert(input);
|
||||
|
||||
ASSERT_THAT(converted, outputTerm + "foo");
|
||||
}
|
||||
|
||||
TEST_P(escaping, empty)
|
||||
{
|
||||
QString input;
|
||||
|
||||
auto converted = convert(input);
|
||||
|
||||
ASSERT_THAT(converted, IsEmpty());
|
||||
}
|
||||
|
||||
TEST_P(escaping, only)
|
||||
{
|
||||
QString input = inputTerm;
|
||||
|
||||
auto converted = convert(input);
|
||||
|
||||
ASSERT_THAT(converted, outputTerm);
|
||||
}
|
||||
|
||||
TEST_P(escaping, nothing)
|
||||
{
|
||||
QString input = "foobar";
|
||||
|
||||
auto converted = convert(input);
|
||||
|
||||
ASSERT_THAT(converted, "foobar");
|
||||
}
|
||||
|
||||
TEST_P(escaping, end)
|
||||
{
|
||||
QString input = "foo" + inputTerm;
|
||||
|
||||
auto converted = convert(input);
|
||||
|
||||
ASSERT_THAT(converted, "foo" + outputTerm);
|
||||
}
|
||||
|
||||
TEST_P(escaping, middle)
|
||||
{
|
||||
QString input = "foo" + inputTerm + "bar";
|
||||
|
||||
auto converted = convert(input);
|
||||
|
||||
ASSERT_THAT(converted, "foo" + outputTerm + "bar");
|
||||
}
|
||||
|
||||
TEST_P(escaping, multiple)
|
||||
{
|
||||
QString input = "foo" + inputTerm + "bar" + inputTerm + "foo";
|
||||
|
||||
auto converted = convert(input);
|
||||
|
||||
ASSERT_THAT(converted, "foo" + outputTerm + "bar" + outputTerm + "foo");
|
||||
}
|
||||
|
||||
TEST_P(escaping, multiple_in_row)
|
||||
{
|
||||
if (name == "deescape_backslash") // would be double backslash
|
||||
return;
|
||||
QString input = "foo" + inputTerm + inputTerm + "foo";
|
||||
|
||||
auto converted = convert(input);
|
||||
|
||||
ASSERT_THAT(converted, "foo" + outputTerm + outputTerm + "foo");
|
||||
}
|
||||
|
||||
TEST_P(escaping, skip_unicode)
|
||||
{
|
||||
QString input = "\\u" + inputTerm + "foo";
|
||||
input.resize(6);
|
||||
|
||||
auto converted = convert(input);
|
||||
|
||||
ASSERT_THAT(converted, input);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
Reference in New Issue
Block a user