]> git.plutz.net Git - lobster/blob - pdiread.sh
merge from confetti
[lobster] / pdiread.sh
1 #!/bin/zsh
2
3 # Copyright 2014 - 2018 Paul Hänsch
4 #
5 # This file is part of Confetti.
6
7 # Confetti is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # Confetti is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with Confetti.  If not, see <http://www.gnu.org/licenses/>. 
19
20 # This is a parsing library for the Personal Data Interchange format (PDI)
21 # PDI is the format for encoding VCard (.vcf) and iCalendar (.ics) files
22
23 [ -n "$include_pdi" ] && return 0
24 include_pdi="$0"
25
26 BR='
27 '
28
29 unescape() {
30   local unescape='s;(^(\\\\)*|[^\\](\\\\)*)\\n;\1\n;g; s;\\(.);\1;g'
31   if [ $# -eq 0 ]; then
32     sed -E "$unescape"
33   else
34     printf %s "$*" \
35     | sed -E "$unescape"
36   fi
37 }
38
39 pdi_load() {
40   # normalise PDI file for processing with pdi_* functions
41   # functions in this library can only be applied to normalised data
42   # Usage example:
43   # data="$(pdi_load file.vcf)"
44
45   sed -srn '
46     # === Read entire file into buffer ===
47     :X $bY; N; bX; :Y s;^.*$;\n&\n;;
48
49     # === Join continuing lines, strip trailing CRs ===
50     s;\r*\n[ \t];;g;
51     s;\r*\n;\n;g;
52
53     # === turn property names to upper case, strip group names ===
54     s;\n([^;:\.\n]+\.)([^;:\n]+);\n\2;g;
55     :upcase
56     s;(\n[^;:]*)a;\1A;g; s;(\n[^;:]*)b;\1B;g; s;(\n[^;:]*)c;\1C;g; s;(\n[^;:]*)d;\1D;g; s;(\n[^;:]*)e;\1E;g;
57     s;(\n[^;:]*)f;\1F;g; s;(\n[^;:]*)g;\1G;g; s;(\n[^;:]*)h;\1H;g; s;(\n[^;:]*)i;\1I;g; s;(\n[^;:]*)j;\1J;g;
58     s;(\n[^;:]*)k;\1K;g; s;(\n[^;:]*)l;\1L;g; s;(\n[^;:]*)m;\1M;g; s;(\n[^;:]*)n;\1N;g; s;(\n[^;:]*)o;\1O;g;
59     s;(\n[^;:]*)p;\1P;g; s;(\n[^;:]*)q;\1Q;g; s;(\n[^;:]*)r;\1R;g; s;(\n[^;:]*)s;\1S;g; s;(\n[^;:]*)t;\1T;g;
60     s;(\n[^;:]*)u;\1U;g; s;(\n[^;:]*)v;\1V;g; s;(\n[^;:]*)w;\1W;g; s;(\n[^;:]*)x;\1X;g; s;(\n[^;:]*)y;\1Y;g;
61     s;(\n[^;:]*)z;\1Z;g;
62     t upcase;
63
64     # === Insert empty attribute fields where no attributes are present ===
65     s;\n([^;:]+):;\n\1\;:;g;
66
67     # === Unscramble aggregated fields ===
68     :disag
69     s;\n([^:\n]+:)(([^\n]*[^\])?(\\\\)*),;\n\1\2\n\1;;
70     t disag;
71
72     # === Insert FN when only N is present ===
73     /\nFN[;:]/!{
74       s,\nN(;[^:]*)?:([^;\n]*);([^;\n]*);([^;\n]*);([^;\n]*);([^;\n]*);?\n,&FN;:\5 \3 \4 \2 \6\n,;
75       :despace
76       s,(\nFN;:[^\n]*)  ([^\n]*\n),\1 \2,;
77       s,(\nFN;:) ([^\n]*\n),\1\2,;
78       s,(\nFN;:[^\n]*) (\n),\1\2,;
79       t despace;
80     }
81     /\nFN[;:]/!{ s,\n(N[;:][^\n]*)\n,&F\1\n,; }  # Fallback
82
83     # === Normalise various known vendor properties ===
84                 s;\nX-MS-CARDPICTURE(\;|:);\nPHOTO\1;g;
85                         s;\nX-GENDER(\;|:);\nGENDER\1;g;
86                    s;\nX-ANNIVERSARY(\;|:);\nANNIVERSARY\1;g;
87          s;\nX-EVOLUTION-ANNIVERSARY(\;|:);\nANNIVERSARY\1;g;
88     s;\nX-KADDRESSBOOK-X-ANNIVERSARY(\;|:);\nANNIVERSARY\1;g;
89             s;\nX-EVOLUTION-BLOG-URL(\;|:);\nURL\1;g;
90                            s;\nAGENT(\;|:);\nRELATED\;VALUE=text\;TYPE=agent\1;g;
91                      s;\nX-ASSISTANT(\;|:);\nRELATED\;VALUE=text\;TYPE=assistant\1;g;
92            s;\nX-EVOLUTION-ASSISTANT(\;|:);\nRELATED\;VALUE=text\;TYPE=assistant\1;g;
93  s;\nX-KADDRESSBOOK-X-ASSISTANTSNAME(\;|:);\nRELATED\;VALUE=text\;TYPE=assistant\1;g;
94                        s;\nX-MANAGER(\;|:);\nRELATED\;VALUE=text\;TYPE=manager\1;g;
95              s;\nX-EVOLUTION-MANAGER(\;|:);\nRELATED\;VALUE=text\;TYPE=manager\1;g;
96    s;\nX-KADDRESSBOOK-X-MANAGERSNAME(\;|:);\nRELATED\;VALUE=text\;TYPE=manager\1;g;
97                         s;\nX-SPOUSE(\;|:);\nRELATED\;VALUE=text\;TYPE=spouse\1;g;
98               s;\nX-EVOLUTION-SPOUSE(\;|:);\nRELATED\;VALUE=text\;TYPE=spouse\1;g;
99      s;\nX-KADDRESSBOOK-X-SPOUSENAME(\;|:);\nRELATED\;VALUE=text\;TYPE=spouse\1;g;
100
101     # === Normalise obsolete vendor IM properties ===
102             s;\nX-AIM((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:aim:;g;
103             s;\nX-ICQ((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:aim:;g;
104     s;\nX-GOOGLE-TALK((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:xmpp:;g;
105          s;\nX-JABBER((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:xmpp:;g;
106             s;\nX-MSN((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:msn:;g;
107           s;\nX-YAHOO((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:ymsgr:;g;
108             s;\nX-SIP((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(sip:)?;\nIMPP\1:sip:;g;
109
110     # === Update obsolete LABEL property ===
111     s;\nLABEL((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(.*)\n;\nADR\1\;LABEL="\5":\n;g;
112
113     p;' "$@"
114 }
115
116 pdi_escape(){
117   local in out=''
118   for in in "$@"; do
119     out="${out}${out:+;}"
120     while [ "$in" ]; do case $in in
121       \\*) out="${out}\\\\"; in="${in#\\}" ;;
122       ,*) out="${out}\\,"; in="${in#,}" ;;
123       \;*) out="${out}\\;"; in="${in#;}" ;;
124       "$BR"*) out="${out}\\n"; in="${in#${BR}}" ;;
125       *) out="${out}${in%%[\\,;${BR}]*}"; in="${in#"${in%%[\\,;${BR}]*}"}" ;;
126     esac; done
127   done
128   printf '%s\n' "$out"
129 }
130
131 pdi_unescape(){
132   local in out=''
133   [ $# -gt 0 ] && in="$*" || in="$(cat)"
134   while [ "$in" ]; do case $in in
135     \\\\*) out="${out}\\"; in="${in#\\\\}" ;;
136     \\n*) out="${out}${BR}"; in="${in#\\n}" ;;
137     \\*) in="${in#\\}" ;;
138     *) out="${out}${in%%\\*}"; in="${in#"${in%%\\*}"}" ;;
139   esac; done
140   printf '%s\n' "$out"
141 }
142
143 pdi_count(){
144   local card="$1" name="$2" rc='' cnt=0
145   while rc="${card#*${BR}${name};}"; do
146     [ "${rc}" != "${card}" ] || break
147     card="$rc"
148     cnt=$(($cnt + 1))
149   done
150   printf %i\\n $cnt
151 }
152
153 pdi_attrib(){
154   local card=":$1" name="$2" cnt="${3:-1}" attr="$4"
155   while [ $cnt -gt 0 ]; do
156     [ "${card#*${BR}${name};}" = "$card" ] && return 1
157     card="${card#*${BR}${name};}"
158     cnt=$((cnt - 1))
159   done
160   card="${card%%:*}"
161   if [ "$attr" ]; then
162     case $card in
163       *\;"$attr"=*) card="${card#*;${attr}=}";;
164       "$attr"=*) card="${card#${attr}=}";;
165       "$attr"|*\;"$attr"|"$attr"\;*|*\;"$attr"\;*) return 0;;
166       *) return 1;;
167     esac
168     case $card in
169       \"*\"\;*|\'*\'\;*)
170         card="${card#[\"\']}"; card="${card%%[\"\'];*}"
171         ;;
172       \"*\"|\'*\')
173         card="${card#[\"\']}"; card="${card%%[\"\']}"
174         ;;
175       *\;*) card="${card%%;*}";;
176     esac
177   fi
178   printf %s\\n "${card}"
179 }
180
181 pdi_value(){
182   local card="${BR}$1" name="$2" cnt="${3:-1}"
183   while [ "$cnt" -gt 0 ]; do
184     [ "${card#*${BR}${name};*:}" = "$card" ] && return 1
185     card="${card#*${BR}${name};*:}"
186     cnt=$((cnt - 1))
187   done
188   printf %s\\n "${card%%${BR}*}"
189 }
190
191 pdi_update_value(){
192   local card="${BR}$1" name="$2" cnt="$3" val="$4"
193   while [ "$cnt" -gt 0 ]; do
194     if [ "${card#*${BR}${name};*:}" = "${card}" ]; then
195        printf '%s\n%s;:' "${card%${BR}END;:VCARD*}" "${name}"
196        card="${BR}END;:VCARD"
197        break;
198     else
199        printf '%s\n%s;' "${card%%${BR}${name};*}" "${name}"
200        card="${card#*${BR}${name};}"
201        printf '%s:' "${card%%:*}"
202        card="${card#*:}"
203     fi
204     cnt=$((cnt - 1))
205   done
206   printf '%s\n%s\n' "$val" "${card#*${BR}}"
207 }
208
209 pdi_update_attrib(){
210   local card="${BR}$1" name="$2" cnt="$3" val="$4"
211   while [ "$cnt" -gt 0 ]; do
212     if [ "${card#*${BR}${name};*:}" = "${card}" ]; then
213        printf '%s\n%s;' "${card%${BR}END;:VCARD*}" "${name}"
214        card=":${BR}END;:VCARD"
215        break;
216     else
217        printf '%s\n%s;' "${card%%${BR}${name};*}" "${name}"
218        card="${card#*${BR}${name};}"
219     fi
220     cnt=$((cnt - 1))
221   done
222   printf '%s:%s\n' "$val" "${card#*:}"
223 }