Skip to content

Commit 9992ff9

Browse files
author
nemal300
committed
Add utf8 shrink functions
commit_hash:eebf8de05f89401d692625c13336aad120b149fc
1 parent c200eda commit 9992ff9

File tree

3 files changed

+101
-0
lines changed

3 files changed

+101
-0
lines changed

util/charset/utf8.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,29 @@ namespace {
8181
Y_ASSERT(false);
8282
return false;
8383
}
84+
85+
// return longest valid utf-8 prefix size in bytes that is less than or equal to |size|
86+
size_t GetLongestUtf8PrefixSize(TStringBuf sb, size_t size, bool robust) {
87+
const unsigned char* beg = reinterpret_cast<const unsigned char*>(sb.data());
88+
const unsigned char* end = reinterpret_cast<const unsigned char*>(sb.data() + sb.size());
89+
const unsigned char* cur = beg;
90+
while (cur < end && static_cast<size_t>(cur - beg) < size) {
91+
size_t runeLen;
92+
if (RECODE_OK != GetUTF8CharLen(runeLen, cur, end)) {
93+
if (robust) {
94+
break;
95+
} else {
96+
ythrow yexception() << "invalid UTF-8 char at pos " << (cur - beg);
97+
}
98+
}
99+
if ((cur - beg) + runeLen > size) {
100+
break;
101+
}
102+
cur += runeLen;
103+
}
104+
return cur - beg;
105+
}
106+
84107
} // namespace
85108

86109
extern const wchar32 BROKEN_RUNE = 0xFFFD;
@@ -169,3 +192,25 @@ TString ToUpperUTF8(TStringBuf s) {
169192
TString ToUpperUTF8(const char* s) {
170193
return ToUpperUTF8(TStringBuf(s));
171194
}
195+
196+
void Utf8TruncateInplace(TString& s, size_t size) {
197+
const size_t prefixSize = GetLongestUtf8PrefixSize(TStringBuf{s}, size, false);
198+
if (prefixSize != s.size()) {
199+
s.resize(prefixSize);
200+
}
201+
}
202+
203+
void Utf8TruncateInplaceRobust(TString& s, size_t size) {
204+
const size_t prefixSize = GetLongestUtf8PrefixSize(TStringBuf{s}, size, true);
205+
if (prefixSize != s.size()) {
206+
s.resize(prefixSize);
207+
}
208+
}
209+
210+
TStringBuf Utf8Truncate(TStringBuf sb Y_LIFETIME_BOUND, size_t size) {
211+
return sb.substr(0, GetLongestUtf8PrefixSize(sb, size, false));
212+
}
213+
214+
TStringBuf Utf8TruncateRobust(TStringBuf sb Y_LIFETIME_BOUND, size_t size) noexcept {
215+
return sb.substr(0, GetLongestUtf8PrefixSize(sb, size, true));
216+
}

util/charset/utf8.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,3 +442,11 @@ bool ToUpperUTF8Impl(const char* beg, size_t n, TString& newString);
442442
TString ToUpperUTF8(const TString& s);
443443
TString ToUpperUTF8(TStringBuf s);
444444
TString ToUpperUTF8(const char* s);
445+
446+
//! cut utf-8 string to fit into |size| bytes
447+
void Utf8TruncateInplace(TString& s, size_t size);
448+
//! cut on a valid utf-8 sequence less or equal |size|
449+
void Utf8TruncateInplaceRobust(TString& s, size_t size);
450+
[[nodiscard]] TStringBuf Utf8Truncate(TStringBuf sb Y_LIFETIME_BOUND, size_t size);
451+
//! on error returns the longest valid utf8 sequence
452+
[[nodiscard]] TStringBuf Utf8TruncateRobust(TStringBuf sb Y_LIFETIME_BOUND, size_t size) noexcept;

util/charset/utf8_ut.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,52 @@ Y_UNIT_TEST_SUITE(TUtfUtilTest) {
123123
wtextScalar.remove(wtextSSE.size());
124124
UNIT_ASSERT(wtextScalar == wtextSSE);
125125
}
126+
127+
Y_UNIT_TEST(TestUtf8TruncateInplace) {
128+
TString s = "Съешь ещё этих мягких французских булок, да выпей же чаю.";
129+
Utf8TruncateInplace(s, 0u);
130+
UNIT_ASSERT_EQUAL(s, "");
131+
132+
s = "Съешь ещё этих мягких французских булок, да выпей же чаю.";
133+
Utf8TruncateInplace(s, 10u);
134+
UNIT_ASSERT_EQUAL(s, "Съешь");
135+
136+
s = "Съешь ещё этих мягких французских булок, да выпей же чаю.";
137+
TString s_copy = s;
138+
Utf8TruncateInplace(s, s.size());
139+
UNIT_ASSERT_EQUAL(s, s_copy);
140+
141+
Utf8TruncateInplace(s, Max());
142+
UNIT_ASSERT_EQUAL(s, s_copy);
143+
}
144+
145+
Y_UNIT_TEST(TestUtf8TruncateCorrupted) {
146+
const TString s = "Съешь ещё этих мягких французских булок, да выпей же чаю.";
147+
TStringBuf corrupted{s, 0u, 21u};
148+
UNIT_ASSERT_EXCEPTION_CONTAINS(Y_UNUSED(Utf8Truncate(corrupted, 21u)), yexception, "invalid UTF-8 char");
149+
UNIT_ASSERT_NO_EXCEPTION(Y_UNUSED(Utf8TruncateRobust(corrupted, 21u)));
150+
TStringBuf fixed = Utf8TruncateRobust(corrupted, 21u);
151+
UNIT_ASSERT_LE(fixed.size(), 21u);
152+
UNIT_ASSERT_EQUAL(fixed, "Съешь ещё э");
153+
}
154+
155+
Y_UNIT_TEST(TestUtf8CutInvalidSuffixInplace) {
156+
TString s = "Съешь ещё этих мягких французских булок, да выпей же чаю.";
157+
s.resize(21);
158+
UNIT_ASSERT_UNEQUAL(s, "Съешь ещё э");
159+
Utf8TruncateInplaceRobust(s, s.size());
160+
UNIT_ASSERT_EQUAL(s, "Съешь ещё э");
161+
}
162+
163+
Y_UNIT_TEST(TestUtf8CutInvalidSuffix) {
164+
TStringBuf sb = "Съешь ещё этих мягких французских булок, да выпей же чаю."sv;
165+
UNIT_ASSERT_EQUAL(Utf8TruncateRobust(sb, sb.size()), sb);
166+
UNIT_ASSERT_EQUAL(Utf8TruncateRobust(sb.substr(0, 21), sb.size()), "Съешь ещё э"sv);
167+
}
168+
169+
Y_UNIT_TEST(TestUtf8TruncateCornerCases) {
170+
UNIT_ASSERT_EQUAL(Utf8Truncate("①②③"sv, 4).size(), 3);
171+
UNIT_ASSERT_VALUES_EQUAL(Utf8Truncate("foobar"sv, Max()), "foobar"sv);
172+
}
173+
126174
} // Y_UNIT_TEST_SUITE(TUtfUtilTest)

0 commit comments

Comments
 (0)