检测Lua中的最后一个字符是否不是多字节

时间:2013-04-12 19:34:57

标签: regex lua corona multibyte

第一个问题。在Lua中确定字符串中的最后一个字符是否不是多字节的最简单方法是什么。或者从字符串中删除最后一个字符的最简单方法是什么。

以下是有效字符串的示例,以及我希望函数的输出为

hello there     --- result should be:   hello ther
anñ             --- result should be:   an
כראע            --- result should be:   כרא
ㅎㄹㅇㅇㅅ       --- result should be:   ㅎㄹㅇㅇ

我需要像

这样的东西
function lastCharacter(string)
    --- some code which will extract the last character only ---
    return lastChar
end

或者如果它更容易

function deleteLastCharacter(string)
--- some code which will output the string minus the last character --- 
    return newString
end

这是我正在进行的道路

local function lastChar(string)
    local stringLength = string.len(string)
    local lastc = string.sub(string,stringLength,stringLength)
    if lastc is a multibyte character then
        local wordTable = {}
        for word in string:gmatch("[\33-\127\192-\255]+[\128-\191]*") do
            wordTable[#wordTable+1] = word
        end
    lastc = wordTable[#wordTable]
end
    return lastc
end

3 个答案:

答案 0 :(得分:8)

首先,请注意Lua的string库中没有任何关于Unicode / mutlibyte编码的函数(源代码:Lua编程,第3版)。就Lua而言,字符串只是由字节组成。如果您使用UTF-8编码的字符串,则由您决定哪个字节组成一个字符。因此,string.len将为您提供字节的数量,而不是字符的数量string.sub将为您提供 bytes 的子字符串,而不是字符的子字符串。

一些UTF-8基础:

如果您需要对Unicode的概念基础知识进行一些更新,那么您应该查看this article

UTF-8是Unicode的一种可能(也是非常重要的)实现 - 可能是您正在处理的那种。与UTF-32和UTF-16相反,它使用可变数量的字节(从1到4)来编码每个字符。特别是,ASCII字符0到127用单个字节表示,因此可以使用UTF-8正确解释ASCII字符串(反之亦然,如果您只使用这128个字符)。所有其他字符都以194到244之间的一个字节开头(这表示编码完整字符后面会有更多字节)。此范围进一步细分,以便您可以从该字节中判断出是否有1,2或3个字节。这些额外的字节称为连续字节,并且保证仅取自128到191的范围。因此,通过查看单个字节,我们知道它在字符中的位置:

  • 如果它在[0,127]中,则为单字节(ASCII)字符
  • 如果它在[128,191]中,那么它就是一个较长角色的一部分而且本身毫无意义
  • 如果它在[191,244]中,则标记较长字符的开头(并告诉我们该字符的长度)

此信息足以计算字符数,将UTF-8字符串拆分为字符,并执行各种其他UTF-8敏感操作。

一些模式匹配基础:

对于手头的任务,我们需要一些Lua的模式匹配结构:

[...]是一个字符类,它匹配类中的单个字符(或者更确切地说是 byte )。例如。 [abc]abc匹配。您可以使用连字符定义范围。因此,[\33-\127]例如匹配从33127的任何一个字节。请注意,\127是一个转义序列,您可以在任何 Lua字符串(而不仅仅是模式)中使用它来通过其数值而不是相应的ASCII字符指定字节。例如,"a""\97"相同。

你可以通过^启动它来否定一个字符类(这样它就可以匹配不是类的任何一个字节。

*重复前一个标记0次或更多次(任意次数 - 尽可能多次)。

$是一个锚点。如果它是模式的最后一个字符,则模式将仅匹配字符串的末尾。

结合所有这些......

......你的问题缩小为一线:

local function lastChar(s)
    return string.match(s, "[^\128-\191][\128-\191]*$")
end

这将匹配不是UTF-8连续字符的字符(即,它是单字节字符,或者是标记较长字符开头的字节)。然后它匹配任意数量的连续字符(由于选择的范围,这不能超过当前字符),然后是字符串的结尾($)。因此,这将为您提供构成字符串中最后一个字符的所有字节。它为您的所有4个示例生成所需的输出。

等效地,您可以使用gsub从字符串中删除最后一个字符:

function deleteLastCharacter(s)
    return string.gsub(s, "[^\128-\191][\128-\191]*$", "")
end

匹配是相同的,但我们不是返回匹配的子字符串,而是将其替换为""(即删除它)并返回修改后的字符串。

答案 1 :(得分:4)

这是另一种方法;它显示了如何在utf8中遍历一串字符:

function butlast (str)
    local i,j,k = 1,0,-1
    while true do
        s,e = string.find(str,".[\128-\191]*",i)
        if s then
            k = j
            j = e
            i = e + 1
        else break end
    end
    return string.sub(str,1,k)
end

样品使用:

> return butlast"כראע"
כרא
> return butlast"ㅎㄹㅇㅇㅅ"
ㅎㄹㅇㅇ
> return butlast"anñ"
an
> return butlast"hello there"
hello ther
> 

答案 2 :(得分:3)

按照prapin的解决方案here

function lastCharacter(str)
  return str:match("[%z\1-\127\194-\244][\128-\191]*$")
end

然后,您可以获取返回值的长度,以查看它是否为多字节;您也可以使用gsub函数将其从字符串中删除:

function deleteLastCharacter(str)
  -- make sure to add "()" around gsub to force it to return only one value
  return(str:gsub("[%z\1-\127\194-\244][\128-\191]*$", ""))
end

for _, str in pairs{"hello there", "anñ", "כראע"} do
  print(str, " -->-- ", deleteLastCharacter(str))
end

请注意,这些模式仅适用于有效的UTF-8字符串。如果您的文件可能无效,则可能需要应用more complex logic