Software / code / prosody-modules
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 }; |