1/* vcard 2 Author: Giménez, Christian. 3 4 Copyright (C) 2018 Giménez, Christian 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 19 13 may 2018 20*/ 21 22 23:- module(vcard, [ 24 vcard//1, 25 contentline//1, 26 name//1, 27 param//1, 28 vcard_file/2 29 ]).
35:- ensure_loaded(library(dcg/basics)). 36 37vcard_file(Path, VCards) :- 38 read_file_to_codes(Path, Codes, []), 39 vcard_entity(VCards, Codes, []). 40 41crlf --> "\r\n". 42 43fold_space --> " ", !. 44fold_space --> "\t". 45 46folded_line([32|Line]) --> fold_space, string_without("\r\n", Line), crlf.
53folded_string(Codes) --> string_without("\r\n", Codes1), crlf,
54 rest_fold(Codes2),
55 {append(Codes1, Codes2, Codes)}.
63rest_fold(Codes) --> folded_line(CodesLine), 64 rest_fold(CodesRest), !, 65 { append(CodesLine, CodesRest, Codes) }. 66rest_fold([]) --> [].
vcard-entity = 1*vcard
*/
75vcard_entity(VCards) --> vcard(VCard), vcard_entity(Rest), !, 76 { append([VCard], Rest, VCards) }. 77vcard_entity([VCard]) --> vcard(VCard). 78 79header --> "BEGIN:VCARD", crlf. --> "END:VCARD", crlf. 81version(Version) --> "VERSION:", string_without("\r\n", Version), crlf.
vcard = "BEGIN:VCARD" CRLF "VERSION:4.0" CRLF 1*contentline "END:VCARD" CRLF ; A vCard object MUST include the VERSION and FN properties. ; VERSION MUST come immediately after BEGIN:VCARD.
*/
95vcard(vcard(version(Version), 96 contents(Contents))) --> header, version(Version), 97 contentlines(Contents), 98 footer. 99 100contentlines([Content|Rest]) --> contentline(Content), contentlines(Rest), !. 101contentlines([Content]) --> contentline(Content).
contentline = [group "."] name *(";" param) ":" value CRLF ; When parsing a content line, folded lines must first ; be unfolded according to the unfolding procedure ; described in Section 3.2. ; When generating a content line, lines longer than 75 ; characters SHOULD be folded according to the folding ; procedure described in Section 3.2.
If Group is not indicated, then an empty list is unifying.
Params is a list of codes.
124contentline(content([], Name, Params, Value)) --> 125 name(Name), {Name \= `END`}, 126 !, params(Params), ":", value(Value). 127contentline(content(Group, Name, Params, Value)) --> 128 group(Group), ".", !, 129 name(Name), params(Params), ":", value(Value).
group = 1*(ALPHA / DIGIT / "-")
*/
138group(Group) --> alpha_or_digit(C), group(C2), !, 139 { append(C, C2, Group) }. 140group(Group) --> alpha_or_digit(Group).
name = iana-token / x-name ; Parsing of the param and value is based on the "name" as ; defined in ABNF sections below. ; Group and name are case-insensitive.
We do not implement the following because iana-token//1 can match them.
name = "SOURCE" / "KIND" / "FN" / "N" / "NICKNAME" / "PHOTO" / "BDAY" / "ANNIVERSARY" / "GENDER" / "ADR" / "TEL" / "EMAIL" / "IMPP" / "LANG" / "TZ" / "GEO" / "TITLE" / "ROLE" / "LOGO" / "ORG" / "MEMBER" / "RELATED" / "CATEGORIES" / "NOTE" / "PRODID" / "REV" / "SOUND" / "UID" / "CLIENTPIDMAP" / "URL" / "KEY" / "FBURL" / "CALADRURI" / "CALURI" / "XML"
*/
165name(Name) --> iana_token(Name). 166 167 168alpha_or_digit([C]) --> digit(C), !. 169alpha_or_digit([C]) --> [C], {code_type(C, alpha)}, !. 170alpha_or_digit(`-`) --> "-". 171 172aods(Chars) --> alpha_or_digit(C), aods(Chars1), !, 173 {append(C, Chars1, Chars)}. 174aods(C) --> alpha_or_digit(C).
iana-token = 1*(ALPHA / DIGIT / "-") ; identifier registered with IANA
*/
185iana_token(Token) --> aods(Token). 186 187x_name_x(`x-`) --> "x-". 188x_name_x(`X-`) --> "X-".
199x_name(Token) --> x_name_x(X), aods(Chars), 200 {append(X, Chars, Token)}. 201 202params([Param|Rest]) --> ";", param(Param), params(Rest), !. 203params([Param]) --> ";", param(Param),!. 204params([]) --> [].
param = language-param / value-param / pref-param / pid-param / type-param / geo-parameter / tz-parameter / sort-as-param / calscale-param / any-param ; Allowed parameters depend on property name.
227param(param(Name, Values)) --> any_param(param(Name, Values)), !.
param-value = *SAFE-CHAR / DQUOTE *QSAFE-CHAR DQUOTE
SAFE-CHAR
and QSAFE-CHAR
will be represented with string_without/2 as it is
used only for defining this predicate.
*/
239param_value(Value) --> "\"", !, qsafe_chars(Value), "\"". 240param_value(Value) --> safe_chars(Value).
Predicate for defining any_param//2.
251param_values(Values) --> param_value(Value), ",", !, param_values(Values1), 252 {append([Value], Values1, Values)}. 253param_values([Value]) --> param_value(Value).
any-param = (iana-token / x-name) "=" param-value *("," param-value)
266any_param(param(Name, Values)) --> iana_token(Name) , "=",
267 param_values(Values).
SAFE-CHAR = WSP / "!" / %x23-39 / %x3C-7E / NON-ASCII ; Any character except CTLs, DQUOTE, ";", ":"
*/
277safe_chars(Chars) --> string_without("\";:", Chars).
QSAFE-CHAR = WSP / "!" / %x23-7E / NON-ASCII ; Any character except CTLs, DQUOTE
*/
287qsafe_chars(Chars) --> string_without("\"", Chars). 288 289/* 290 NON-ASCII = UTF8-2 / UTF8-3 / UTF8-4 291 ; UTF8-{2,3,4} are defined in [RFC3629] 292 293 VALUE-CHAR = WSP / VCHAR / NON-ASCII 294 ; Any textual character 295*/
language-param = "LANGUAGE=" Language-Tag ; Language-Tag is defined in section 2.1 of RFC 5646
*/
306language_param(`LANGUAGE`, Tag) --> "LANGUAGE=", language_tag(Tag).
value-param = "VALUE=" value-type value-type = "text" / "uri" / "date" / "time" / "date-time" / "date-and-or-time" / "timestamp" / "boolean" / "integer" / "float" / "utc-offset" / "language-tag" / iana-token ; registered as described in section 12 / x-name
*/
331value_param(Type) --> "VALUE=", value_type(Type). 332 333value_type(`text`) --> "text", !. 334value_type(`uri`) --> "uri",!. 335value_type(`date`) --> "date",!. 336value_type(`time`) --> "time",!. 337value_type(`date-time`) --> "date-time",!. 338value_type(`date-and-or-time`) --> "date-and-or-time",!. 339value_type(`timestamp`) --> "timestamp",!. 340value_type(`boolean`) --> "boolean",!. 341value_type(`integer`) --> "integer",!. 342value_type(`float`) --> "float",!. 343value_type(`utc-offset`) --> "utc-offset",!. 344value_type(`language-tag`) --> "language-tag",!. 345value_type(Type) --> iana_token(Type), !. 346value_type(Type) --> x_name(Type), !.
pref-param = "PREF=" (1*2DIGIT / "100") ; An integer between 1 and 100.
*/
358pref_param(Num) --> "PREF=", digits(D),
359 {number_codes(Num, D),
360 Num =< 100}.
altid-param = "ALTID=" param-value
*/
373altid_param(Value) --> "ALTID=", param_value(Value).
pid-param = "PID=" pid-value *("," pid-value) pid-value = 1*DIGIT ["." 1*DIGIT]
*/
383pid_param(Value) --> "PID=", pid_values(Value). 384pid_values([Value|Rest]) --> pid_value(Value), ",", !,
385 pid_values(Rest)
385. 386pid_values([Value]) --> pid_value(Value). 387 388pid_value(Value) --> digits(Value1), ".", !,
389 digits(Value2), 390 {append([Value1, `.`, Value2], Value)}
390. 391pid_value(Value) --> digits(Value).
`
*/403type_param(Value) --> "TYPE=", type_values(Value). 404 405type_value(`work`) --> "work", !. 406type_value(`home`) --> "home", !. 407type_value(Value) --> type_param_tel(Value), !. 408type_value(Value) --> type_param_related(Value), !. 409type_value(Value) --> iana_token(Value), !. 410type_value(Value) --> x_name(Value).
mediatype-param = "MEDIATYPE=" mediatype mediatype = type-name "/" subtype-name *( ";" attribute "=" value ) ; "attribute" and "value" are from [RFC2045] ; "type-name" and "subtype-name" are from [RFC4288]
*/
422mediatype_param(type(Types, [], [])) --> 423 "MEDIATYPE=", mediatype(Types). 424mediatype(type(Types, SubTypes, Attrs)) --> 425 type_name(Types), "/", subtype_name(SubTypes), 426 attributes(Attrs). 427 428attributes([Attr|Rest]) --> attribute(Attr), attributes(Rest), !. 429attributes([Attr]) --> attribute(Attr).
438attribute(attribute(Attr, Value)) --> ";", attribute_b(Attr), "=", value_b(Value).
token := 1*<any (US-ASCII) CHAR except SPACE, CTLs, or tspecials> tspecials := "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / "\" / <"> "/" / "[" / "]" / "?" / "=" ; Must be in quoted-string, ; to use within parameter values
458attribute_b(Token) --> string_without(" ()<>@,;:\\\"/[]?=", Token). 459 460value_b(Str) --> attribute_b(Str), !. 461value_b(Str) --> quoted_string_b(Str).
467quoted_string_b(Str) --> "\"", string(Str), "\"", !.
474value(Value) --> folded_string(Value)
vcard: vCard Parser Implementation.
*/