1 -- Copyright (C) 2011-2014 Kim Alvefur
3 -- This project is MIT/X11 licensed. Please see the
4 -- COPYING file in the source package for more information.
10 local st
= require
"util.stanza";
11 local t_insert
, t_concat
= table.insert
, table.concat
;
13 local pairs
, ipairs
= pairs
, ipairs
;
15 local from_text
, to_text
, from_xep54
, to_xep54
;
17 local line_sep
= "\n";
19 local vCard_dtd
; -- See end of file
22 local function vCard_esc(s
)
23 return s
:gsub("[,:;\\]", "\\%1"):gsub("\n","\\n");
26 local function vCard_unesc(s
)
27 return s
:gsub("\\?[\\nt:;,]", {
32 ["\\:"] = ":", -- FIXME Shouldn't need to espace : in values, just params
41 local function item_to_xep54(item
)
42 local t
= st
.stanza(item
.name
, { xmlns
= "vcard-temp" });
44 local prop_def
= vCard_dtd
[item
.name
];
45 if prop_def
== "text" then
47 elseif type(prop_def
) == "table" then
48 if prop_def
.types
and item
.TYPE
then
49 if type(item
.TYPE
) == "table" then
50 for _
,v
in pairs(prop_def
.types
) do
51 for _
,typ
in pairs(item
.TYPE
) do
52 if typ
:upper() == v
then
59 t
:tag(item
.TYPE
:upper()):up();
63 if prop_def
.props
then
64 for _
,prop
in pairs(prop_def
.props
) do
66 for _
, v
in ipairs(item
[prop
]) do
73 if prop_def
.value
then
74 t
:text_tag(prop_def
.value
, item
[1]);
75 elseif prop_def
.values
then
76 local prop_def_values
= prop_def
.values
;
77 local repeat_last
= prop_def_values
.behaviour
== "repeat-last" and prop_def_values
[#prop_def_values
];
79 t
:text_tag(prop_def
.values
[i
] or repeat_last
, item
[i
]);
87 local function vcard_to_xep54(vCard
)
88 local t
= st
.stanza("vCard", { xmlns
= "vcard-temp" });
90 t
:add_child(item_to_xep54(vCard
[i
]));
95 function to_xep54(vCards
)
96 if not vCards
[1] or vCards
[1].name
then
97 return vcard_to_xep54(vCards
)
99 local t
= st
.stanza("xCard", { xmlns
= "vcard-temp" });
101 t
:add_child(vcard_to_xep54(vCards
[i
]));
107 function from_text(data
)
108 data
= data
-- unfold and remove empty lines
114 for line
in data
:gmatch("[^\n]+") do
115 line
= vCard_unesc(line
);
116 local name
, params
, value
= line
:match("^([-%a]+)(\30?[^\29]*)\29(.*)$");
117 value
= value
:gsub("\29",":");
120 for k
,isval
,v
in params
:gmatch("\30([^=]+)(=?)([^\30]*)") do
123 for _p
in v
:gmatch("[^\31]+") do
135 if name
== "BEGIN" and value
== "VCARD" then
137 vCards
[#vCards
+1] = current
;
138 elseif name
== "END" and value
== "VCARD" then
140 elseif current
and vCard_dtd
[name
] then
141 local dtd
= vCard_dtd
[name
];
142 local item
= { name
= name
};
143 t_insert(current
, item
);
147 for _
, t
in ipairs(dtd
.types
) do
149 if ( params
.TYPE
and params
.TYPE
[t
] == true)
150 or params
[t
] == true then
156 for _
, p
in ipairs(dtd
.props
) do
158 if params
[p
] == true then
161 for _
, prop
in ipairs(params
[p
]) do
168 if dtd
== "text" or dtd
.value
then
169 t_insert(current
, value
);
170 elseif dtd
.values
then
171 for p
in ("\30"..value
):gmatch("\30([^\30]*)") do
172 t_insert(current
, p
);
181 local function item_to_text(item
)
184 value
[i
] = vCard_esc(item
[i
]);
186 value
= t_concat(value
, ";");
189 for k
,v
in pairs(item
) do
190 if type(k
) == "string" and k
~= "name" then
191 params
= params
.. (";%s=%s"):format(k
, type(v
) == "table" and t_concat(v
,",") or v
);
195 return ("%s%s:%s"):format(item
.name
, params
, value
)
198 local function vcard_to_text(vcard
)
200 t_insert(t
, "BEGIN:VCARD")
202 t_insert(t
, item_to_text(vcard
[i
]));
204 t_insert(t
, "END:VCARD")
205 return t_concat(t
, line_sep
);
208 function to_text(vCards
)
209 if vCards
[1] and vCards
[1].name
then
210 return vcard_to_text(vCards
)
214 t
[i
]=vcard_to_text(vCards
[i
]);
216 return t_concat(t
, line_sep
);
220 local function from_xep54_item(item
)
221 local prop_name
= item
.name
;
222 local prop_def
= vCard_dtd
[prop_name
];
224 local prop
= { name
= prop_name
};
226 if prop_def
== "text" then
227 prop
[1] = item
:get_text();
228 elseif type(prop_def
) == "table" then
229 if prop_def
.value
then --single item
230 prop
[1] = item
:get_child_text(prop_def
.value
) or "";
231 elseif prop_def
.values
then --array
232 local value_names
= prop_def
.values
;
233 if value_names
.behaviour
== "repeat-last" then
234 for i
=1,#item
.tags
do
235 t_insert(prop
, item
.tags
[i
]:get_text() or "");
238 for i
=1,#value_names
do
239 t_insert(prop
, item
:get_child_text(value_names
[i
]) or "");
242 elseif prop_def
.names
then
243 local names
= prop_def
.names
;
245 if item
:get_child(names
[i
]) then
252 if prop_def
.props_verbatim
then
253 for k
,v
in pairs(prop_def
.props_verbatim
) do
258 if prop_def
.types
then
259 local types
= prop_def
.types
;
262 if item
:get_child(types
[i
]) then
263 t_insert(prop
.TYPE
, types
[i
]:lower());
266 if #prop
.TYPE
== 0 then
271 -- A key-value pair, within a key-value pair?
272 if prop_def
.props
then
273 local params
= prop_def
.props
;
275 local name
= params
[i
]
276 local data
= item
:get_child_text(name
);
278 prop
[name
] = prop
[name
] or {};
279 t_insert(prop
[name
], data
);
290 local function from_xep54_vCard(vCard
)
291 local tags
= vCard
.tags
;
294 t_insert(t
, from_xep54_item(tags
[i
]));
299 function from_xep54(vCard
)
300 if vCard
.attr
.xmlns
~= "vcard-temp" then
301 return nil, "wrong-xmlns";
303 if vCard
.name
== "xCard" then -- A collection of vCards
305 local vCards
= vCard
.tags
;
307 t
[i
] = from_xep54_vCard(vCards
[i
]);
310 elseif vCard
.name
== "vCard" then -- A single vCard
311 return from_xep54_vCard(vCard
)
317 function vcard4
:text(node
, params
, value
) -- luacheck: ignore 212/params
318 self
:tag(node
:lower())
320 if type(value
) == "string" then
321 self
:text_tag("text", value
);
322 elseif vcard4
[node
] then
328 function vcard4
.N(value
)
329 for i
, k
in ipairs(vCard_dtd
.N
.values
) do
330 value
:text_tag(k
, value
[i
]);
334 local xmlns_vcard4
= "urn:ietf:params:xml:ns:vcard-4.0"
336 local function item_to_vcard4(item
)
337 local typ
= item
.name
:lower();
338 local t
= st
.stanza(typ
, { xmlns
= xmlns_vcard4
});
340 local prop_def
= vCard4_dtd
[typ
];
341 if prop_def
== "text" then
342 t
:text_tag("text", item
[1]);
343 elseif prop_def
== "uri" then
344 if item
.ENCODING
and item
.ENCODING
[1] == 'b' then
345 t
:text_tag("uri", "data:;base64," .. item
[1]);
347 t
:text_tag("uri", item
[1]);
349 elseif type(prop_def
) == "table" then
350 if prop_def
.values
then
351 for i
, v
in ipairs(prop_def
.values
) do
352 t
:text_tag(v
:lower(), item
[i
]);
355 t
:tag("unsupported",{xmlns
="http://zash.se/protocol/vcardlib"})
358 t
:tag("unsupported",{xmlns
="http://zash.se/protocol/vcardlib"})
363 local function vcard_to_vcard4xml(vCard
)
364 local t
= st
.stanza("vcard", { xmlns
= xmlns_vcard4
});
366 t
:add_child(item_to_vcard4(vCard
[i
]));
371 local function vcards_to_vcard4xml(vCards
)
372 if not vCards
[1] or vCards
[1].name
then
373 return vcard_to_vcard4xml(vCards
)
375 local t
= st
.stanza("vcards", { xmlns
= xmlns_vcard4
});
377 t
:add_child(vcard_to_vcard4xml(vCards
[i
]));
383 -- This was adapted from http://xmpp.org/extensions/xep-0054.html#dtd
385 VERSION
= "text", --MUST be 3.0, so parsing is redundant
398 props_verbatim
= { ENCODING
= { "b" } },
400 value
= "BINVAL", --{ "EXTVAL", },
474 LOGO
= "copy of PHOTO",
478 behaviour
= "repeat-last",
490 SOUND
= "copy of PHOTO",
494 names
= { -- The item.name is the value if it's one of these.
506 vCard_dtd
.LOGO
= vCard_dtd
.PHOTO
;
507 vCard_dtd
.SOUND
= vCard_dtd
.PHOTO
;
525 bday
= "date-and-or-time",
526 anniversary
= "date-and-or-time",
542 lang
= "language-tag",
557 clientpidmap
= "number, uuid",
567 from_text
= from_text
;
570 from_xep54
= from_xep54
;
573 to_vcard4
= vcards_to_vcard4xml
;