Software /
code /
prosody
Comparison
util/vcard.lua @ 6262:e24027bafb0e
util.vcard: Library for parsing vCards
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Wed, 28 May 2014 20:12:13 +0200 |
child | 6263:e208950446c8 |
comparison
equal
deleted
inserted
replaced
6261:be8eab694d6c | 6262:e24027bafb0e |
---|---|
1 -- Copyright (C) 2011-2014 Kim Alvefur | |
2 -- | |
3 -- This project is MIT/X11 licensed. Please see the | |
4 -- COPYING file in the source package for more information. | |
5 -- | |
6 | |
7 -- TODO | |
8 -- Fix folding. | |
9 | |
10 local st = require "util.stanza"; | |
11 local t_insert, t_concat = table.insert, table.concat; | |
12 local type = type; | |
13 local next, pairs, ipairs = next, pairs, ipairs; | |
14 | |
15 local from_text, to_text, from_xep54, to_xep54; | |
16 | |
17 local line_sep = "\n"; | |
18 | |
19 local vCard_dtd; -- See end of file | |
20 | |
21 local function fold_line() | |
22 error "Not implemented" --TODO | |
23 end | |
24 local function unfold_line() | |
25 error "Not implemented" | |
26 -- gsub("\r?\n[ \t]([^\r\n])", "%1"); | |
27 end | |
28 | |
29 local function vCard_esc(s) | |
30 return s:gsub("[,:;\\]", "\\%1"):gsub("\n","\\n"); | |
31 end | |
32 | |
33 local function vCard_unesc(s) | |
34 return s:gsub("\\?[\\nt:;,]", { | |
35 ["\\\\"] = "\\", | |
36 ["\\n"] = "\n", | |
37 ["\\r"] = "\r", | |
38 ["\\t"] = "\t", | |
39 ["\\:"] = ":", -- FIXME Shouldn't need to espace : in values, just params | |
40 ["\\;"] = ";", | |
41 ["\\,"] = ",", | |
42 [":"] = "\29", | |
43 [";"] = "\30", | |
44 [","] = "\31", | |
45 }); | |
46 end | |
47 | |
48 local function item_to_xep54(item) | |
49 local t = st.stanza(item.name, { xmlns = "vcard-temp" }); | |
50 | |
51 local prop_def = vCard_dtd[item.name]; | |
52 if prop_def == "text" then | |
53 t:text(item[1]); | |
54 elseif type(prop_def) == "table" then | |
55 if prop_def.types and item.TYPE then | |
56 if type(item.TYPE) == "table" then | |
57 for _,v in pairs(prop_def.types) do | |
58 for _,typ in pairs(item.TYPE) do | |
59 if typ:upper() == v then | |
60 t:tag(v):up(); | |
61 break; | |
62 end | |
63 end | |
64 end | |
65 else | |
66 t:tag(item.TYPE:upper()):up(); | |
67 end | |
68 end | |
69 | |
70 if prop_def.props then | |
71 for _,v in pairs(prop_def.props) do | |
72 if item[v] then | |
73 t:tag(v):up(); | |
74 end | |
75 end | |
76 end | |
77 | |
78 if prop_def.value then | |
79 t:tag(prop_def.value):text(item[1]):up(); | |
80 elseif prop_def.values then | |
81 local prop_def_values = prop_def.values; | |
82 local repeat_last = prop_def_values.behaviour == "repeat-last" and prop_def_values[#prop_def_values]; | |
83 for i=1,#item do | |
84 t:tag(prop_def.values[i] or repeat_last):text(item[i]):up(); | |
85 end | |
86 end | |
87 end | |
88 | |
89 return t; | |
90 end | |
91 | |
92 local function vcard_to_xep54(vCard) | |
93 local t = st.stanza("vCard", { xmlns = "vcard-temp" }); | |
94 for i=1,#vCard do | |
95 t:add_child(item_to_xep54(vCard[i])); | |
96 end | |
97 return t; | |
98 end | |
99 | |
100 function to_xep54(vCards) | |
101 if not vCards[1] or vCards[1].name then | |
102 return vcard_to_xep54(vCards) | |
103 else | |
104 local t = st.stanza("xCard", { xmlns = "vcard-temp" }); | |
105 for i=1,#vCards do | |
106 t:add_child(vcard_to_xep54(vCards[i])); | |
107 end | |
108 return t; | |
109 end | |
110 end | |
111 | |
112 function from_text(data) | |
113 data = data -- unfold and remove empty lines | |
114 :gsub("\r\n","\n") | |
115 :gsub("\n ", "") | |
116 :gsub("\n\n+","\n"); | |
117 local vCards = {}; | |
118 local c; -- current item | |
119 for line in data:gmatch("[^\n]+") do | |
120 local line = vCard_unesc(line); | |
121 local name, params, value = line:match("^([-%a]+)(\30?[^\29]*)\29(.*)$"); | |
122 value = value:gsub("\29",":"); | |
123 if #params > 0 then | |
124 local _params = {}; | |
125 for k,isval,v in params:gmatch("\30([^=]+)(=?)([^\30]*)") do | |
126 k = k:upper(); | |
127 local _vt = {}; | |
128 for _p in v:gmatch("[^\31]+") do | |
129 _vt[#_vt+1]=_p | |
130 _vt[_p]=true; | |
131 end | |
132 if isval == "=" then | |
133 _params[k]=_vt; | |
134 else | |
135 _params[k]=true; | |
136 end | |
137 end | |
138 params = _params; | |
139 end | |
140 if name == "BEGIN" and value == "VCARD" then | |
141 c = {}; | |
142 vCards[#vCards+1] = c; | |
143 elseif name == "END" and value == "VCARD" then | |
144 c = nil; | |
145 elseif c and vCard_dtd[name] then | |
146 local dtd = vCard_dtd[name]; | |
147 local p = { name = name }; | |
148 c[#c+1]=p; | |
149 --c[name]=p; | |
150 local up = c; | |
151 c = p; | |
152 if dtd.types then | |
153 for _, t in ipairs(dtd.types) do | |
154 local t = t:lower(); | |
155 if ( params.TYPE and params.TYPE[t] == true) | |
156 or params[t] == true then | |
157 c.TYPE=t; | |
158 end | |
159 end | |
160 end | |
161 if dtd.props then | |
162 for _, p in ipairs(dtd.props) do | |
163 if params[p] then | |
164 if params[p] == true then | |
165 c[p]=true; | |
166 else | |
167 for _, prop in ipairs(params[p]) do | |
168 c[p]=prop; | |
169 end | |
170 end | |
171 end | |
172 end | |
173 end | |
174 if dtd == "text" or dtd.value then | |
175 t_insert(c, value); | |
176 elseif dtd.values then | |
177 local value = "\30"..value; | |
178 for p in value:gmatch("\30([^\30]*)") do | |
179 t_insert(c, p); | |
180 end | |
181 end | |
182 c = up; | |
183 end | |
184 end | |
185 return vCards; | |
186 end | |
187 | |
188 local function item_to_text(item) | |
189 local value = {}; | |
190 for i=1,#item do | |
191 value[i] = vCard_esc(item[i]); | |
192 end | |
193 value = t_concat(value, ";"); | |
194 | |
195 local params = ""; | |
196 for k,v in pairs(item) do | |
197 if type(k) == "string" and k ~= "name" then | |
198 params = params .. (";%s=%s"):format(k, type(v) == "table" and t_concat(v,",") or v); | |
199 end | |
200 end | |
201 | |
202 return ("%s%s:%s"):format(item.name, params, value) | |
203 end | |
204 | |
205 local function vcard_to_text(vcard) | |
206 local t={}; | |
207 t_insert(t, "BEGIN:VCARD") | |
208 for i=1,#vcard do | |
209 t_insert(t, item_to_text(vcard[i])); | |
210 end | |
211 t_insert(t, "END:VCARD") | |
212 return t_concat(t, line_sep); | |
213 end | |
214 | |
215 function to_text(vCards) | |
216 if vCards[1] and vCards[1].name then | |
217 return vcard_to_text(vCards) | |
218 else | |
219 local t = {}; | |
220 for i=1,#vCards do | |
221 t[i]=vcard_to_text(vCards[i]); | |
222 end | |
223 return t_concat(t, line_sep); | |
224 end | |
225 end | |
226 | |
227 local function from_xep54_item(item) | |
228 local prop_name = item.name; | |
229 local prop_def = vCard_dtd[prop_name]; | |
230 | |
231 local prop = { name = prop_name }; | |
232 | |
233 if prop_def == "text" then | |
234 prop[1] = item:get_text(); | |
235 elseif type(prop_def) == "table" then | |
236 if prop_def.value then --single item | |
237 prop[1] = item:get_child_text(prop_def.value) or ""; | |
238 elseif prop_def.values then --array | |
239 local value_names = prop_def.values; | |
240 if value_names.behaviour == "repeat-last" then | |
241 for i=1,#item.tags do | |
242 t_insert(prop, item.tags[i]:get_text() or ""); | |
243 end | |
244 else | |
245 for i=1,#value_names do | |
246 t_insert(prop, item:get_child_text(value_names[i]) or ""); | |
247 end | |
248 end | |
249 elseif prop_def.names then | |
250 local names = prop_def.names; | |
251 for i=1,#names do | |
252 if item:get_child(names[i]) then | |
253 prop[1] = names[i]; | |
254 break; | |
255 end | |
256 end | |
257 end | |
258 | |
259 if prop_def.props_verbatim then | |
260 for k,v in pairs(prop_def.props_verbatim) do | |
261 prop[k] = v; | |
262 end | |
263 end | |
264 | |
265 if prop_def.types then | |
266 local types = prop_def.types; | |
267 prop.TYPE = {}; | |
268 for i=1,#types do | |
269 if item:get_child(types[i]) then | |
270 t_insert(prop.TYPE, types[i]:lower()); | |
271 end | |
272 end | |
273 if #prop.TYPE == 0 then | |
274 prop.TYPE = nil; | |
275 end | |
276 end | |
277 | |
278 -- A key-value pair, within a key-value pair? | |
279 if prop_def.props then | |
280 local params = prop_def.props; | |
281 for i=1,#params do | |
282 local name = params[i] | |
283 local data = item:get_child_text(name); | |
284 if data then | |
285 prop[name] = prop[name] or {}; | |
286 t_insert(prop[name], data); | |
287 end | |
288 end | |
289 end | |
290 else | |
291 return nil | |
292 end | |
293 | |
294 return prop; | |
295 end | |
296 | |
297 local function from_xep54_vCard(vCard) | |
298 local tags = vCard.tags; | |
299 local t = {}; | |
300 for i=1,#tags do | |
301 t_insert(t, from_xep54_item(tags[i])); | |
302 end | |
303 return t | |
304 end | |
305 | |
306 function from_xep54(vCard) | |
307 if vCard.attr.xmlns ~= "vcard-temp" then | |
308 return nil, "wrong-xmlns"; | |
309 end | |
310 if vCard.name == "xCard" then -- A collection of vCards | |
311 local t = {}; | |
312 local vCards = vCard.tags; | |
313 for i=1,#vCards do | |
314 t[i] = from_xep54_vCard(vCards[i]); | |
315 end | |
316 return t | |
317 elseif vCard.name == "vCard" then -- A single vCard | |
318 return from_xep54_vCard(vCard) | |
319 end | |
320 end | |
321 | |
322 -- This was adapted from http://xmpp.org/extensions/xep-0054.html#dtd | |
323 vCard_dtd = { | |
324 VERSION = "text", --MUST be 3.0, so parsing is redundant | |
325 FN = "text", | |
326 N = { | |
327 values = { | |
328 "FAMILY", | |
329 "GIVEN", | |
330 "MIDDLE", | |
331 "PREFIX", | |
332 "SUFFIX", | |
333 }, | |
334 }, | |
335 NICKNAME = "text", | |
336 PHOTO = { | |
337 props_verbatim = { ENCODING = { "b" } }, | |
338 props = { "TYPE" }, | |
339 value = "BINVAL", --{ "EXTVAL", }, | |
340 }, | |
341 BDAY = "text", | |
342 ADR = { | |
343 types = { | |
344 "HOME", | |
345 "WORK", | |
346 "POSTAL", | |
347 "PARCEL", | |
348 "DOM", | |
349 "INTL", | |
350 "PREF", | |
351 }, | |
352 values = { | |
353 "POBOX", | |
354 "EXTADD", | |
355 "STREET", | |
356 "LOCALITY", | |
357 "REGION", | |
358 "PCODE", | |
359 "CTRY", | |
360 } | |
361 }, | |
362 LABEL = { | |
363 types = { | |
364 "HOME", | |
365 "WORK", | |
366 "POSTAL", | |
367 "PARCEL", | |
368 "DOM", | |
369 "INTL", | |
370 "PREF", | |
371 }, | |
372 value = "LINE", | |
373 }, | |
374 TEL = { | |
375 types = { | |
376 "HOME", | |
377 "WORK", | |
378 "VOICE", | |
379 "FAX", | |
380 "PAGER", | |
381 "MSG", | |
382 "CELL", | |
383 "VIDEO", | |
384 "BBS", | |
385 "MODEM", | |
386 "ISDN", | |
387 "PCS", | |
388 "PREF", | |
389 }, | |
390 value = "NUMBER", | |
391 }, | |
392 EMAIL = { | |
393 types = { | |
394 "HOME", | |
395 "WORK", | |
396 "INTERNET", | |
397 "PREF", | |
398 "X400", | |
399 }, | |
400 value = "USERID", | |
401 }, | |
402 JABBERID = "text", | |
403 MAILER = "text", | |
404 TZ = "text", | |
405 GEO = { | |
406 values = { | |
407 "LAT", | |
408 "LON", | |
409 }, | |
410 }, | |
411 TITLE = "text", | |
412 ROLE = "text", | |
413 LOGO = "copy of PHOTO", | |
414 AGENT = "text", | |
415 ORG = { | |
416 values = { | |
417 behaviour = "repeat-last", | |
418 "ORGNAME", | |
419 "ORGUNIT", | |
420 } | |
421 }, | |
422 CATEGORIES = { | |
423 values = "KEYWORD", | |
424 }, | |
425 NOTE = "text", | |
426 PRODID = "text", | |
427 REV = "text", | |
428 SORTSTRING = "text", | |
429 SOUND = "copy of PHOTO", | |
430 UID = "text", | |
431 URL = "text", | |
432 CLASS = { | |
433 names = { -- The item.name is the value if it's one of these. | |
434 "PUBLIC", | |
435 "PRIVATE", | |
436 "CONFIDENTIAL", | |
437 }, | |
438 }, | |
439 KEY = { | |
440 props = { "TYPE" }, | |
441 value = "CRED", | |
442 }, | |
443 DESC = "text", | |
444 }; | |
445 vCard_dtd.LOGO = vCard_dtd.PHOTO; | |
446 vCard_dtd.SOUND = vCard_dtd.PHOTO; | |
447 | |
448 return { | |
449 from_text = from_text; | |
450 to_text = to_text; | |
451 | |
452 from_xep54 = from_xep54; | |
453 to_xep54 = to_xep54; | |
454 }; |