]> git.plutz.net Git - confetti/commitdiff
vcard parser in lua
authorPaul Hänsch <paul@plutz.net>
Thu, 22 Aug 2024 13:07:52 +0000 (15:07 +0200)
committerPaul Hänsch <paul@plutz.net>
Thu, 22 Aug 2024 13:07:52 +0000 (15:07 +0200)
lua/vcard.lua [new file with mode: 0644]

diff --git a/lua/vcard.lua b/lua/vcard.lua
new file mode 100644 (file)
index 0000000..1794c06
--- /dev/null
@@ -0,0 +1,109 @@
+#!/usr/bin/lua
+
+local Vcard = {}
+
+-- constructor, synonymous to load()
+function Vcard:new(path)
+  return self:load(path)
+end
+
+-- constructor
+-- load and parse vcard file, return new vcard object
+function Vcard:load(path)
+  local file, data
+
+  file = io.open(path)
+  if not file then return nil; end
+  data = file:read("a")
+  file:close()
+
+  return self:parse(data)
+end
+
+-- constructor
+-- parse text block of a vcard, return new vcard object
+function Vcard:parse(data)
+  local line, key, attr, value, i, v
+  local r = {}
+
+  -- unwrap line continuations, remove carriage return at EOL
+  data = data:gsub("\r?\n[ \t]", "")
+  data = data:gsub("\r\n", "\n")
+
+  -- parse lines into fields
+  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
+      table.insert(r[key], v)
+      r[key].attr[#r[key]] = attr
+    end
+  end
+
+  setmetatable(r, { __index = self })
+  return r
+end
+
+-- internal
+-- split vcard line into key, value, and an array of attributes
+function Vcard:_parse_line(line)
+  local key, value
+  local attr = {}
+
+  key = line:match('^([^:;]+)[;:].*$')
+  if not key then return nil end
+
+  line = line:sub(#key + 1 )
+  a = line:match('^;([^";:]+)[;:].*$') or line:match('^;([^"=;:]+="[^"]+")[;:].*$')
+  while a do
+    table.insert(attr, a)
+    line = line:sub(#a + 2)
+    a = line:match('^;([^";:]+)[;:].*$') or line:match('^;([^"=;:]+="[^"]+")[;:].*$')
+  end
+
+  value = line:match("^:(.*)$")
+  if not value then return nil end
+
+  key = key:upper()
+  return key, attr, value
+end
+
+-- internal
+-- split value fields aggregated by comma ignoring escaped commas
+function Vcard:_split_line(line)
+  local f
+  local r = { "" }
+
+  while line ~= "" and line ~= "\\" do
+    if line:match("^,") then
+      table.insert(r, "")
+      line = line:sub(2)
+    end
+    f = line:match("^(\\.)") or line:match("^([^\\,]+)")
+    r[#r] = r[#r] .. f
+    line = line:sub(#f + 1)
+  end
+  return r
+end
+
+-- internal
+-- development tests
+function Vcard:_test()
+  local vcf = Vcard:parse([=[
+BEGIN:VCARD
+VERSION:1.0
+UID:1
+N:Lastname;Firstname;Middle Names; Title; Suffix
+PHONE;TYPE=Home:123456789,666999
+PHONE;TYPE=Work;TYPE=Cell:987654321
+END:VCARD
+]=])
+
+  assert( vcf["PHONE"][1] == "123456789", "Phone/1 wrong number" )
+  assert( vcf["PHONE"][2] == "666999", "Phone/2 wrong number" )
+  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" )
+end
+
+return Vcard