mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-11-11 22:01:50 +08:00
fix(tokenizer): fold Cyrillic before stemming so Russian keywords match (#21433)
This commit is contained in:
1 parent
1ab7dbf5a8
commit
f7de7669ad
3 files changed
+119
-2
No files matched your search
@@ -113,6 +113,84 @@ inline std::string ToLowerString(std::string_view s)
|
||||
return result;
|
||||
}
|
||||
|
||||
// Simple (1:1) lower-case mapping for Cyrillic U+0400-U+04FF. Upper/lower
|
||||
// pairs are contiguous inside the main blocks; U+0460-U+0481 and
|
||||
// U+048A-U+04BF interleave them (upper first), while U+04C3-U+04CE interleave
|
||||
// with the upper case on the odd code point. U+04C0/U+04C1 are the two
|
||||
// exceptions Unicode defines outside that pattern.
|
||||
[[nodiscard]] constexpr uint32_t ToLowerCyrillic(uint32_t cp) noexcept
|
||||
{
|
||||
if (cp >= 0x0400 && cp <= 0x040F)
|
||||
return cp + 0x50;
|
||||
if (cp >= 0x0410 && cp <= 0x042F)
|
||||
return cp + 0x20;
|
||||
if (cp >= 0x0460 && cp <= 0x0481 && (cp & 1) == 0)
|
||||
return cp + 1;
|
||||
if (cp >= 0x048A && cp <= 0x04BF && (cp & 1) == 0)
|
||||
return cp + 1;
|
||||
if (cp == 0x04C0)
|
||||
return 0x04CF;
|
||||
if (cp == 0x04C1)
|
||||
return 0x04C2;
|
||||
if (cp >= 0x04C3 && cp <= 0x04CE && (cp & 1) == 1)
|
||||
return cp + 1;
|
||||
if (cp >= 0x04D0 && cp <= 0x04FF && (cp & 1) == 0)
|
||||
return cp + 1;
|
||||
return cp;
|
||||
}
|
||||
|
||||
// ToLowerUTF8 folds ASCII A-Z and upper-case Cyrillic U+0400-U+04FF to lower
|
||||
// case in place. Byte length is preserved so callers that map token offsets
|
||||
// back to the source string keep their indices valid. The Snowball Russian
|
||||
// stemmer only accepts lower-case input, so index-side tokens must be folded
|
||||
// before stemming; ToLower above is deliberately ASCII-only and leaves
|
||||
// Cyrillic untouched.
|
||||
inline void ToLowerUTF8(char* data, size_t len)
|
||||
{
|
||||
for (size_t i = 0; i < len;)
|
||||
{
|
||||
const unsigned char c = static_cast<unsigned char>(data[i]);
|
||||
if (c < 0x80)
|
||||
{
|
||||
if ('A' <= c && c <= 'Z')
|
||||
data[i] += 32;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
// Cyrillic occupies the two-byte leads D0-D3. All foldings stay inside
|
||||
// the same block, so the encoded length never changes.
|
||||
if (c >= 0xD0 && c <= 0xD3 && i + 1 < len)
|
||||
{
|
||||
const unsigned char t = static_cast<unsigned char>(data[i + 1]);
|
||||
if (t >= 0x80 && t <= 0xBF)
|
||||
{
|
||||
const uint32_t cp = ((c - 0xD0u) << 6) + (t - 0x80u) + 0x400u;
|
||||
const uint32_t folded = ToLowerCyrillic(cp);
|
||||
if (folded != cp)
|
||||
{
|
||||
const uint32_t offset = folded - 0x400u;
|
||||
data[i] = static_cast<char>(0xD0u + (offset >> 6));
|
||||
data[i + 1] = static_cast<char>(0x80u + (offset & 0x3Fu));
|
||||
}
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Other multi-byte sequences pass through byte for byte.
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Copies `len` bytes into `out`, folds them and terminates the result. Callers
|
||||
// must provide len + 1 bytes: byte length is preserved, so that is the only
|
||||
// space the fold needs.
|
||||
inline void ToLowerUTF8(const char* data, size_t len, char* out)
|
||||
{
|
||||
memcpy(out, data, len);
|
||||
ToLowerUTF8(out, len);
|
||||
out[len] = '\0';
|
||||
}
|
||||
|
||||
inline bool IsUTF8Sep(const uint8_t c) { return c < 128 && !std::isalnum(c); }
|
||||
|
||||
template <typename T>
|
||||
|
||||
Reference in new issue
Block a user