快速掌握Lua 5.3 —— 字符串库

Posted VermillionTear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速掌握Lua 5.3 —— 字符串库相关的知识,希望对你有一定的参考价值。

Q:什么情况下”pattern”会匹配空串?

A:要小心的使用*-,因为它们可以匹配零次。

-- 如果你打算用"%a*"匹配单词,你会发现到处都是单词。
print(string.find(";$%  **#$hello13", "%a*"))    --> 1    0
print(string.find(";$%  **#$hello13", "%a*", 6))    --> 6    5
-- 使用"%a+"才能正常的完成工作。
print(string.find(";$%  **#$hello13", "%a+"))    --> 10    14

Q:如何使用Lua生成”pattern”?

A:使用Lua可以帮我们生成一些繁琐的”pattern”,

--[[ 查找一个文本中行字符大于70个的行,也就是匹配一个非换行符之前有70个字符的行。
     重复匹配单个字符70次,后面跟着一个匹配单个字符0次或多次。]]
pattern = string.rep("[^\\n]", 70) .. "[^\\n]*"

-- 对于单词进行大小写无关的查找。
function nocase (s)
    -- 每找到一个子母,就将其转换为"[xX]"的形式。
    s = string.gsub(s, "%a", function (c)
            return string.format("[%s%s]", string.lower(c),
                                           string.upper(c))
        end)
    return s
end
pattern = nocase("Hi there!")
print(pattern)    -->  [hH][iI] [tT][hH][eE][rR][eE]!

Q:如何对目标串进行预处理?

A:预处理的意义在于排除特殊字符对匹配的影响。
第一个例子,将字符串中双引号内的字符串转换为大写,双引号之间可以包含转义的双引号\\"

-- 将转义的双引号转换为"\\ddd"的形式,其中"ddd"是双引号ASCII码的十进制表示。
function code (s)
    return (string.gsub(s, "\\\\(.)", function (x)
        return string.format("\\\\%03d", string.byte(x))
    end))
end

-- 将"\\ddd"恢复为转义的双引号。
function decode (s)
    return (string.gsub(s, "\\\\(%d%d%d)", function (d)
        return "\\\\" .. string.char(d)
    end))
end

s = [[follows a typical string: "This is \\"great\\"!".]]
--[[ 省去这步预处理,"great"将不会被转换为大写。
     因为"This is \\"是第一次匹配,"!"是第二次匹配。]]
s = code(s)
-- 使用"string.upper()"转换为大写。
s = string.gsub(s, '(".-")', string.upper)
s = decode(s)    -- 将预处理的部分复原。
print(s)    --> follows a typical string: "THIS IS \\"GREAT\\"!".

第二个例子,我们来扩展“快速掌握Lua 5.3 —— 字符串库 (2)”中提到的”LaTeX”格式转为”XML”格式的例子。这一次的”LaTeX”格式中可以包含转义字符\\,也就是说可以使用\\\\\\{\\},分别表示 \\{}

--[[ 为了避免"\\emph"和"\\command"与"\\{a\\\\b\\}"混淆在一起,
     我们首先应该将"\\{"、"\\\\"和"\\}"转换为特殊的编码,
     然而与此同时不能将"\\emph"和"\\command"转换,
     所以仅当"\\"后面不是子母的时候才进行转换。
     与上一个例子相同,转换为他们的"\\ddd"的十进制形式。]]
function code (s)
    return (string.gsub(s, '\\\\(%A)', function (x)
        return string.format("\\\\%03d", string.byte(x))
    end))
end

-- 与上个例子相比,解码的时候不需要"\\"了,所以可以直接调用"string.char()"。
function decode (s)
    return (string.gsub(s, '\\\\(%d%d%d)', string.char))
end

s = [[a \\emph{command} is written as \\command{text\\{a\\\\b\\}}.]]
s = code(s)
s = string.gsub(s, "\\\\(%a+){(.-)}", "<%1>%2</%1>")
print(decode(s))
--> a <emph>command</emph> is written as <command>text{a\\b}</command>.

第三个例子是个稍微复杂的例子,我们来编解码”CSV”文件。
1、”CSV”文件的每一行表示一条记录,每一条记录由多个域组成,每一个域之间使用,分隔。
2、每一个域中的任何空格字符都是有效字符,不能被忽略。
3、如果域中包含,,那么整个域需要用""引起来,如果此时域中还包含",那么每一个"使用""代替。
4、不包含,的域也可以选择性的使用""引起来。但只要是使用""引起来的域,如果其中包含",那么每一个"都必须使用""代替。
根据以上规则,如若有表
t = {'a b', 'a,b', '', ' a,"b"c', 'hello "world"!'}
其中的元素转换为”CSV”文件格式应为,
a b,"a,b"," a,""b""c",,hello "world"!

print("Encode: ")
function escapeCSV (s)
    if string.find(s, ',') then    -- 如果域中有逗号,则整个域需要用双引号引起来。
        -- 如果此时域中还有双引号,则每个双引号都应使用两个连续的双引号代替。
        s = '"' .. string.gsub(s, '"', '""') .. '"'
    end
    return s
end

function toCSV (t)
    local s = ""
    for _,p in pairs(t) do
        s = s .. "," .. escapeCSV(p)    -- 每个域使用","分隔。
    end
    return string.sub(s, 2)    -- remove first comma
end

t = {'a b', 'a,b', ' a,"b"c', '', 'hello "world"!'}
s = toCSV(t)
print(s)    --> a b,"a,b"," a,""b""c",,hello "world"!
print()

print("Decode: ")
function fromCSV (s)
    s = s .. ','    -- 在字符串末尾添加",",为了方便查找最后一个域。
    local t = {}    -- table to collect fields
    local fieldstart = 1    -- 域在字符串中的起始索引。
    -- 循环的找出一条记录中的每一个域。
    repeat
        -- 如果域的起始位置是一个双引号,那么说明整个域被双引号引了起来。
        if string.find(s, '^"', fieldstart) then
            local a, c
            local i  = fieldstart
            repeat
                --[[ 查找域尾的双引号。
                     因为如果被双引号引起来的域中含有双引号字符的话,
                     每个双引号都会使用连续的两个双引号代替。
                     那么当找到连续的两个双引号时,"c"得到的是后面的那个双引号,
                     "i"得到的是该双引号的索引位置,所以从"i+1"处继续寻找,
                     即跳过了这两个连续的双引号继续寻找。
                     而当找到一个双引号跟着一个非双引号字符时,此时找到了域尾的双引号。
                     "c"得到的是个空字符串,"i"得到了双引号的索引位置。此时,完成查找。]]
                a, i, c = string.find(s, '"("?)', i+1)
            until c ~= '"'    -- quote not followed by quote?
            if not i then error('unmatched "') end
            -- 取出该域,舍弃域首尾的双引号。
            local f = string.sub(s, fieldstart+1, i-1)
            --[[ 因为已经舍弃了域首尾的双引号,
                 所以域中所有连续的两个双引号恢复为单个双引号。
                 将域的内容存入"table"中。]]
            table.insert(t, (string.gsub(f, '""', '"')))
            --[[ "i"是域尾双引号的索引位置,从此处寻找与下一个域之间的逗号,
                 并更新下一个域的起始索引位置。
                 因为在函数开始时,将最后一个域的末尾也加上了逗号,
                 所以按照规范的写法,域尾双引号之后的字符就应该是逗号,
                 所以此处实际可以写成"fieldstart = i + 2",
                 而以下这种写法是为了防止不规范的写法,
                 即域尾双引号与域间逗号之间有空格字符。]]
            fieldstart = string.find(s, ',', i) + 1
        else    -- 如果域的起始位置不是一个双引号,那么说明整个域未被双引号引起来。
            -- 直接寻找下一个域间的逗号。
            local nexti = string.find(s, ',', fieldstart)
            -- 将域的内容存入"table"中。
            table.insert(t, string.sub(s, fieldstart, nexti-1))
            fieldstart = nexti + 1    -- 更新下一个域的起始索引位置。
        end
    until fieldstart > string.len(s)
    return t
end

t = fromCSV(s)
for i, s in ipairs(t) do print(i, s) end
--[[ result: 
     1  a b
     2  a,b
     3   a,"b"c
     4  
     5  hello "world"!]]
print()
--[[ 测试两个连续的双引号的不同作用。
     每个域的内容依次为:
     1、hello,空格,双引号,空格,hello。
     2、空格,双引号,双引号。
     3、无。]]
t = fromCSV('"hello "" hello", "",""')
for i, s in ipairs(t) do print(i, s) end
--[[ result: 
     1  hello " hello
     2   ""
     3  ]]

以上是关于快速掌握Lua 5.3 —— 字符串库的主要内容,如果未能解决你的问题,请参考以下文章

快速掌握Lua 5.3 —— I/O库

快速掌握Lua 5.3 —— 调试库

快速掌握Lua 5.3 —— 调试库

快速掌握Lua 5.3 —— 编写提供给Lua使用的C库函数的技巧

快速掌握Lua 5.3 —— 编写提供给Lua使用的C库函数的技巧

快速掌握Lua 5.3 —— userdata