不区分大小写的Lua模式匹配

20

我正在为我们运行Windows CE 6/7的移动设备编写Lua中的grep实用程序,但在实现不区分大小写的匹配模式时遇到了一些问题。显而易见的解决方案是将所有内容转换为大写(或小写),但由于字符类别的原因,这并不简单。

我能想到的唯一另一个解决方案是将模式中的文字本身转换为大写。

这是我的代码:

function toUpperPattern(instr)
    -- Check first character
    if string.find(instr, "^%l") then
        instr = string.upper(string.sub(instr, 1, 1)) .. string.sub(instr, 2)
    end
    -- Check the rest of the pattern
    while 1 do
        local a, b, str = string.find(instr, "[^%%](%l+)")
        if not a then break end
        if str then
            instr = string.sub(instr, 1, a) .. string.upper(string.sub(instr, a+1, b)) .. string.sub(instr, b + 1)
        end
    end
    return instr
end

我不想承认,即使走了这么远,仍然可以立刻看到百分号'%%'等转义字符会有问题。

我认为这应该是一个相当常见的问题,但我似乎找不到太多相关的资料。 是否有更简单(或至少完整)的方法来解决这个问题?我在这里开始变得疯狂... 希望各位Lua专家能给我指点迷津!

2个回答

16

尝试类似以下代码:

function case_insensitive_pattern(pattern)

  -- find an optional '%' (group 1) followed by any character (group 2)
  local p = pattern:gsub("(%%?)(.)", function(percent, letter)

    if percent ~= "" or not letter:match("%a") then
      -- if the '%' matched, or `letter` is not a letter, return "as is"
      return percent .. letter
    else
      -- else, return a case-insensitive character class of the matched letter
      return string.format("[%s%s]", letter:lower(), letter:upper())
    end

  end)

  return p
end

print(case_insensitive_pattern("xyz = %d+ or %% end"))

打印出:

[xX][yY][zZ] = %d+ [oO][rR] %% [eE][nN][dD]

1
太棒了,我一度不知道该怎么办。顺便说一下:你可以像使用letter:lower一样使用pattern:gsub。甚至可以使用('[%s%s]'):format,但那有点奇怪。 - Mud
是的,string.format(...) 看起来比 ('[%s%s]'):format(...) 更熟悉,但我更喜欢 pattern:gsub(...)!谢谢。 - Bart Kiers
太不可思议了。但是有一个问题...为什么它不会将%%test转换为%%[tT]est?这个匹配被跳过了,因为之前的迭代已经匹配了两个'%%'吗?也许我的大脑今天有点疲惫 :/ - Nubbychadnezzar
@Nubbychadnezzar,%%test 被转换为 %%[tT][eE][sS][tT]。一旦一个模式匹配成功,它将不会成为另一个匹配的一部分。因此,%%test 有5个匹配项:%%test%% 保持不变,并且字母被转换为 [tT][eE]... - Bart Kiers
@Bart “一旦一个模式匹配成功,它将永远不会成为另一个匹配的一部分。” 这正是我需要听到的。没有理解这一点是我沮丧的主要原因!谢谢! - Nubbychadnezzar
1
注意:此函数目前无法处理包含方括号的模式。例如,“答案是[ABCD]”。 - Stomp

2
Lua 5.1,LPeg v0.12
do
    local p = re.compile([[
        pattern  <- ( {b} / {escaped} / brackets / other)+
        b        <- "%b" . .
        escaped  <- "%" .
        brackets <- { "[" ([^]%]+ / escaped)* "]" }
        other    <- [^[%]+ -> cases
    ]], {
        cases = function(str) return (str:gsub('%a',function(a) return '['..a:lower()..a:upper()..']' end)) end
    })
    local pb = re.compile([[
        pattern  <- ( {b} / {escaped} / brackets / other)+
        b        <- "%b" . .
        escaped  <- "%" .
        brackets <- {: {"["} ({escaped} / bcases)* {"]"} :}
        bcases   <- [^]%]+ -> bcases
        other    <- [^[%]+ -> cases
    ]], {
        cases = function(str) return (str:gsub('%a',function(a) return '['..a:lower()..a:upper()..']' end)) end
        , bcases = function(str) return (str:gsub('%a',function(a) return a:lower()..a:upper() end)) end
    })
    function iPattern(pattern,brackets)
        ('sanity check'):find(pattern)
        return table.concat({re.match(pattern, brackets and pb or p)})
    end
end

local test                  = '[ab%c%]d%%]+ o%%r %bnm'
print(iPattern(test))       -- [ab%c%]d%%]+ [oO]%%[rR] %bnm
print(iPattern(test,true))  -- [aAbB%c%]dD%%]+ [oO]%%[rR] %bnm
print(('qwe [%D]% O%r n---m asd'):match(iPattern(test, true))) -- %D]% O%r n---m

纯Lua版本:

为了将字符串转换为正确的模式,必须分析字符串中的所有字符,因为Lua模式不像正则表达式(abc|something)那样具有替代项。

function iPattern(pattern, brackets)
    ('sanity check'):find(pattern)
    local tmp = {}
    local i=1
    while i <= #pattern do              -- 'for' don't let change counter
        local char = pattern:sub(i,i)   -- current char
        if char == '%' then
            tmp[#tmp+1] = char          -- add to tmp table
            i=i+1                       -- next char position
            char = pattern:sub(i,i)
            tmp[#tmp+1] = char
            if char == 'b' then         -- '%bxy' - add next 2 chars
                tmp[#tmp+1] = pattern:sub(i+1,i+2)
                i=i+2
            end
        elseif char=='[' then           -- brackets
            tmp[#tmp+1] = char
            i = i+1
            while i <= #pattern do
                char = pattern:sub(i,i)
                if char == '%' then     -- no '%bxy' inside brackets
                    tmp[#tmp+1] = char
                    tmp[#tmp+1] = pattern:sub(i+1,i+1)
                    i = i+1
                elseif char:match("%a") then    -- letter
                    tmp[#tmp+1] = not brackets and char or char:lower()..char:upper()
                else                            -- something else
                    tmp[#tmp+1] = char
                end
                if char==']' then break end -- close bracket
                i = i+1
            end
        elseif char:match("%a") then    -- letter
            tmp[#tmp+1] = '['..char:lower()..char:upper()..']'
        else
            tmp[#tmp+1] = char          -- something else
        end
        i=i+1
    end
    return table.concat(tmp)
end

local test                  = '[ab%c%]d%%]+ o%%r %bnm'
print(iPattern(test))       -- [ab%c%]d%%]+ [oO]%%[rR] %bnm
print(iPattern(test,true))  -- [aAbB%c%]dD%%]+ [oO]%%[rR] %bnm
print(('qwe [%D]% O%r n---m asd'):match(iPattern(test, true))) -- %D]% O%r n---m

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接