快速掌握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 —— 编写提供给Lua使用的C库函数的技巧