9
18
2012
12

截短 UTF-8 字符串

本文来自依云's Blog,转载请注明。

用于显示时,经常会遇到显示的文本太长需要截短的情况。如果是如 ASCII 这样的定长编码,截短到指定长度自然不成问题。可如果源字符串是 UTF-8 编码的呢?ANSI C 里只管字节不管编码,所以如果想只用 ANSI C 提供的功能的话,就只能自己写了。因为需求仅仅是截短字符串而已,也不要求多么精确,所以没有去做编解码,只是丢弃按字节截短后的字符串最后的无效编码而已。而且目标语种是 Lua,也不方便搞位操作。

维基百科可知,UTF-8 多字节字符第一字节的最高两位为11,而其它字节的最高两位均为10。所以就把后面那些10xxxxxx连同最开始的11xxxxxx去掉好了。这样会多截掉一个多字节字符,但无所谓了。

function truncateUTF8String(s, n)
  local r = string.sub(s, 1, n)
  local last = string.byte(r, n)
  if not last then return r end
  while last >= 128 and last <= 192 do
    n = n - 1
    r = string.sub(r, 1, n)
    last = string.byte(r, n)
  end
  if last >= 128 then
    r = string.sub(r, 1, n-1)
  end
  return r
end

2012年9月27日更新:感谢Fermat618提供的思路,更新了更简洁、准确的代码如下:

function truncateUTF8String(s, n)
  local dropping = string.byte(s, n+1)
  if not dropping then return s end
  if dropping >= 128 and dropping < 192 then
    return truncateUTF8String(s, n-1)
  end
  return string.sub(s, 1, n)
end
Category: 编程 | Tags: 乱码 Lua 中文支持 | Read Count: 14013
maplebeats 说:
Sep 18, 2012 10:28:50 PM

仙子怎么又开始写lua了

Avatar_small
依云 说:
Sep 18, 2012 11:09:45 PM

怎么了,有什么问题吗?

Mike 说:
Sep 19, 2012 01:10:48 PM

http://developer.gnome.org/glib/2.31/glib-Unicode-Manipulation.html#g-utf8-substring

Avatar_small
依云 说:
Sep 19, 2012 02:11:26 PM

glibc 我都不想依赖,你觉得我会用 glib 的东西吗?

Mike 说:
Sep 19, 2012 10:02:15 PM

可以參考一下實現?

Avatar_small
依云 说:
Sep 20, 2012 11:49:57 AM

闭着眼睛都知道它的实现是 O(n) 的。

Fermat618 说:
Sep 27, 2012 12:38:20 AM

not last 这种把字符串用,实作是丑陋的用法。这种写法对于一个不懂 lua 细节的人根本看不懂,而且,这种写法在其它的语言中完全可能代表完全不同的意思。

Fermat618 说:
Sep 27, 2012 12:39:02 AM

not last 这种把字符串当逻辑值用,实作是丑陋的用法。这种写法对于一个不懂 lua 细节的人根本看不懂,而且,这种写法在其它的语言中完全可能代表完全不同的意思。

Avatar_small
依云 说:
Sep 27, 2012 09:06:22 AM

last 是数字或者 nil,什么时候成字符串了?从语义上讲,string.byte 返回了一个为假的东西,那就是失败了。反倒是 Python 的 str.find 以及 JavaScript 的 String.indexOf 在失败时返回 -1 经常让人搞错。

Fermat618 说:
Sep 27, 2012 01:03:59 PM

既然 -1 代表错误,那就把结果和 -1 比较一下就行了。-1 是一个 indexof 正常运行明显不可能返回的值,用它来代表错误也比直接把它当逻辑值用意思清楚得多。到底用什么来代表出错明显是由具体实现来定义的,用时就该查文档查个明白,你搞错说明你编程习惯差,依赖某些语言中养成的不良风格。

如果 string.byte 返回一个 0, 你说它是真是假?别说 0 了,在 php 字符串 "0" 都是假。vimscript 里面字符串 "true" 都是假。把非逻辑值直接当逻辑值的用法恶心之处在于它在什么是真什么时候是假没有一个明确的显而易见的定义,怎么解释完全得看解释器。

依赖于未定义操作也不是好习惯。在一个不足 n 字节长的字符串的 n 处取字符,会得到什么,这具体怎么定义依赖于不同实现的东西。你这里也许是返回了一个 nil. 其它地方也许是拋出异常,甚至导致程序异常终止(如C语言)。

你似乎有使用语言的各处阴暗旮旯角落癖好。好的风格应该是尽力避开语言的阴暗角落,写出明白易懂的程序。

这个程序,开始为什么没有依据N小于0,等于0,小于字符串长度,大于字符串长度这几种情况做出明确定义呢。该抛错误的地方就主动抛个错误,加上错误信息,这轮子不就更圆了么。

另外,last <= 应该改成 last < 192 吧。

这程序换一种逻辑会更清楚了些,如果截取处下一个字符 (n+1处)可以作为一个UTF-8字符的开始(0x11xx 或 0x0xx),就从此处截断,或非,n-1 处再试一下这个操作。在保证了 n 不会越界后, 伪代码写出来就是 trunc(s, n) = if isUTF8Prefix(s(n+1)) then s(0:n) else trunc(s, n-1) 换成循环也很直接。

Avatar_small
依云 说:
Sep 27, 2012 02:15:31 PM

我这里写的是 Lua,其真假值有着明确的定义。string.byte 的返回值亦是语言本身明确定义的。

后边那个算法似乎很好,有空我试试。

Fermat618 说:
Sep 27, 2012 07:15:02 PM

它在每一门语言里面都会有明确定义的。vimscript 中就是把那个字符串先转换成数字,如果非零就为真。Python 中是长度不为0就是真。PHP会比较复杂一点。但对于“怎么”定义,并没有一个明确的答案。这才是问题。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

| Theme: Aeros 2.0 by TheBuckmaker.com