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