Comparison

mod_vjud/vcard.lib.lua @ 716:dac33b8f190b

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