std::字符串 在安全位置截断 UTF-8 的最佳方法

std::string optimal way to truncate utf-8 at safe place

本文关键字:UTF-8 方法 最佳 位置 字符串 安全 std      更新时间:2023-10-16

我在 std::string 中有一个有效的 utf-8 编码字符串。我有字节限制。我想截断字符串并添加...at MAX_SIZE - 3 - x - 其中x是防止剪切 UTF-8 字符的值。

是否有函数可以根据MAX_SIZE确定x而无需从字符串的开头开始?

如果您在字符串中有一个位置,并且想要向后查找 UTF-8 字符的开头(因此是一个有效的剪切位置(,这很容易完成。

从序列中的最后一个字节开始。如果最后一个字节的前两位10,那么它是 UTF-8 序列的一部分,因此请继续备份,直到前两位未10(或直到您到达起点(。

UTF-8 的工作方式是,基于字节的上位,字节可以是三种情况之一。如果最上面的位是 0 ,则字节是 ASCII 字符,接下来的 7 位是 Unicode 码位值本身。如果最上面的位是10,那么后面的 6 位是多字节序列的额外位。但是多字节序列的开头在前 2 位中使用11进行编码。

因此,如果一个字节的顶部位不10,那么它要么是 ASCII 字符,要么是多字节序列的开头。无论哪种方式,它都是一个有效的切割场所。

但请注意,虽然此算法将在代码点边界处中断字符串,但它会忽略 Unicode 字形簇。这意味着可以剔除组合字符,远离它们组合的基本字符;例如,字符中的重音可能会丢失。进行正确的字形聚类分析需要访问 Unicode 表,该表指示代码点是否为组合字符。

但它至少是一个有效的 Unicode UTF-8 字符串。所以这比大多数人做的要好;)


代码如下所示(在 C++14 中(:

auto FindCutPosition(const std::string &str, size_t max_size)
{
  assert(str.size() >= max_size, "Make sure stupidity hasn't happened.");
  assert(str.size() > 3, "Make sure stupidity hasn't happened.");
  max_size -= 3;
  for(size_t pos = max_size; pos > 0; --pos)
  {
    unsigned char byte = static_cast<unsigned char>(str[pos]); //Perfectly valid
    if(byte & 0xC0 != 0x80)
      return pos;
  }
  unsigned char byte = static_cast<unsigned char>(str[0]); //Perfectly valid
  if(byte & 0xC0 != 0x80)
    return 0;
  //If your first byte isn't even a valid UTF-8 starting point, then something terrible has happened.
  throw bad_utf8_encoded_text(...);
}