]> git.plutz.net Git - confetti/commitdiff
lua field splitting and escaping
authorPaul Hänsch <paul@plutz.net>
Thu, 5 Sep 2024 12:23:52 +0000 (14:23 +0200)
committerPaul Hänsch <paul@plutz.net>
Thu, 5 Sep 2024 12:23:52 +0000 (14:23 +0200)
lua/vcard.lua

index 1794c0614b7d934b037ac1d714e1652f50604467..ab812485e052fd4c4fae1b7258f3660035087c74 100644 (file)
@@ -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