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