From: Paul Hänsch Date: Thu, 5 Sep 2024 12:23:52 +0000 (+0200) Subject: lua field splitting and escaping X-Git-Url: https://git.plutz.net/?a=commitdiff_plain;h=870866afe1c0271d71fdbafe8f5a93e2e0929d99;p=confetti lua field splitting and escaping --- diff --git a/lua/vcard.lua b/lua/vcard.lua index 1794c06..ab81248 100644 --- a/lua/vcard.lua +++ b/lua/vcard.lua @@ -23,7 +23,7 @@ end -- constructor -- parse text block of a vcard, return new vcard object function Vcard:parse(data) - local line, key, attr, value, i, v + local line, key, attr, value, i, v, n local r = {} -- unwrap line continuations, remove carriage return at EOL @@ -34,21 +34,61 @@ function Vcard:parse(data) for line in data:gmatch("[^\n]+") do key, attr, value = self:_parse_line(line) if not r[key] then r[key] = { attr = {} } end - for i,v in ipairs(self:_split_line(value)) do + for i,v in ipairs(self:_split_by(value, ",")) do table.insert(r[key], v) r[key].attr[#r[key]] = attr end end + -- try to ensure existence of FN field + if not r["FN"] and r["N"] then + n = self:components(r["N"][1]) + r["FN"] = { (n[4] or " ") .. " " .. (n[2] or " ") .. " " .. + (n[3] or " ") .. " " .. (n[1] or " ") .. " " .. + (n[5] or " ") } + r["FN"][1] = r["FN"][1]:gsub("%s+", " "):gsub("^ ", ""):gsub(" $", "") + end + if not r["FN"] and r["NICKNAME"] then + r["FN"] = { r["NICKNAME"][1] } + r["FN"][1] = r["FN"][1]:gsub("%s+", " "):gsub("^ ", ""):gsub(" $", "") + end + setmetatable(r, { __index = self }) return r end +-- split field components (i.e. the N field) +function Vcard:components(line) + return self:_split_by(line, ";") +end + +function Vcard:unescape(line) + local f local out = "" + + while line ~= "" and line ~= "\\" do + if (function() f = line:match("^\\n([^\\]*)") return f end)() then + out = out .. "\n" .. f + line = line:sub(#f + 3) + elseif (function() f = line:match("^\\(.[^\\]*)") return f end)() then + out = out .. f + line = line:sub(#f + 2) + elseif (function() f = line:match("^([^\\]+)") return f end)() then + out = out .. f + line = line:sub(#f + 1) + end + end + + return out +end + +function Vcard:escape(line) + return line:gsub("\\", "\\\\"):gsub(",", "\\,"):gsub(";", "\\;"):gsub("\n", "\\n") +end + -- internal -- split vcard line into key, value, and an array of attributes function Vcard:_parse_line(line) - local key, value - local attr = {} + local key, value local attr = {} key = line:match('^([^:;]+)[;:].*$') if not key then return nil end @@ -69,17 +109,16 @@ function Vcard:_parse_line(line) end -- internal --- split value fields aggregated by comma ignoring escaped commas -function Vcard:_split_line(line) - local f - local r = { "" } +-- split string by specified character, unless the character is ecapped by \ +function Vcard:_split_by(line, split) + local f local r = { "" } while line ~= "" and line ~= "\\" do - if line:match("^,") then + if line:match("^" .. split) then table.insert(r, "") line = line:sub(2) end - f = line:match("^(\\.)") or line:match("^([^\\,]+)") + f = line:match("^(\\.)") or line:match("^([^\\" .. split .. "]+)") r[#r] = r[#r] .. f line = line:sub(#f + 1) end @@ -89,11 +128,12 @@ end -- internal -- development tests function Vcard:_test() - local vcf = Vcard:parse([=[ + local vcf = self:parse([=[ BEGIN:VCARD VERSION:1.0 UID:1 N:Lastname;Firstname;Middle Names; Title; Suffix +NICKNAME: Bone Crusher, Cookie Monster PHONE;TYPE=Home:123456789,666999 PHONE;TYPE=Work;TYPE=Cell:987654321 END:VCARD @@ -104,6 +144,20 @@ END:VCARD assert( vcf["PHONE"].attr[2][1] == vcf["PHONE"].attr[1][1] ) assert( vcf["PHONE"][3] == "987654321", "Phone/3 wrong number" ) assert( vcf["PHONE"].attr[3][2] == "TYPE=Cell", "Phone/3 attr type=cell" ) + assert( vcf["FN"][1] == "Title Firstname Middle Names Lastname Suffix" ) + + vcf = self:parse([=[ +BEGIN:VCARD +VERSION:1.0 +UID:1 +NICKNAME: Bone Crusher, Cookie Monster +PHONE;TYPE=Home:123456789,666999 +PHONE;TYPE=Work;TYPE=Cell:987654321 +END:VCARD +]=]) + assert( vcf["FN"][1] == "Bone Crusher" ) + + print("Vcard OK") end return Vcard