From 6660ac34581deea50b81efa696e04015cc2298bb Mon Sep 17 00:00:00 2001 From: =?utf8?q?Paul=20H=C3=A4nsch?= Date: Thu, 22 Aug 2024 15:07:52 +0200 Subject: [PATCH] vcard parser in lua --- lua/vcard.lua | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 lua/vcard.lua diff --git a/lua/vcard.lua b/lua/vcard.lua new file mode 100644 index 0000000..1794c06 --- /dev/null +++ b/lua/vcard.lua @@ -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 -- 2.39.2