unify vcf parsers
[confetti] / pages / cards.sh
1 #!/bin/zsh
2
3 # Copyright 2014 - 2016 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 BR='
21 '
22
23 case $PROFILE in
24 medical)
25   view_card="$_EXEC/templates/view_client.sh"
26   edit_card="$_EXEC/templates/edit_client.sh"
27   _GET[order]="${_GET[order]:-lastname}"
28   _GET[filtertype]="${_GET[filtertype]:-name}"
29   profile_medical=x
30 ;;
31 circus)
32   view_card="$_EXEC/templates/view_attendee.sh"
33   edit_card="$_EXEC/templates/edit_attendee.sh"
34   _GET[order]="${_GET[order]:-firstname}"
35   _GET[filtertype]="${_GET[filtertype]:-any}"
36   profile_circus=x
37 ;;
38 esac
39
40 edit="${_GET[edit]}"
41 [ \! -f "vcard/$edit" -a \! -f "temp/$edit" ] && edit=''
42
43 listcourses() {
44   ls -1 ${_DATA}/ical/*ics |while read file; do
45     icstime="$(sed -rn 's:^DTSTART\:(TZID=.*\:)?([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z?\r$:\2-\3-\4 \5\:\6\:\7:p' "$file")"
46     echo "$(date -d "$icstime" "+%u %H%M%S")\t$file"
47   done |sort |sed -r 's:^.*\t(.*/)([^/]+)$:\2:'
48 }
49
50 list_hi_companies(){
51   sed -rn 's;^X-HEALTH-INSURANCE:([^\;]+)\;.*$;\1;p' ${_DATA}/vcard/*vcf \
52   | sort -u
53 }
54
55 listcards() {
56   case "${_GET[filtertype]}" in
57     any)
58        grep -il "${_GET[filter]}" ${_DATA}/vcard/*vcf
59       ;;
60     name)
61        egrep -xil "(FN|NICKNAME|N)(;.+)*:.*${_GET[filter]}.*" ${_DATA}/vcard/*vcf
62       ;;
63     street)
64        egrep -xil "(ADR)(;.+)*:([^;]*;){2}${_GET[filter]}.*" ${_DATA}/vcard/*vcf
65       ;;
66     zip)
67        egrep -xil "(ADR)(;.+)*:([^;]*;){5}${_GET[filter]}.*" ${_DATA}/vcard/*vcf
68       ;;
69     telephone)
70        egrep -xil "(TEL)(;.+)*:.*${_GET[filter]}.*" ${_DATA}/vcard/*vcf
71       ;;
72     birth)
73        egrep -xil "(BDAY)(;.+)*:${_GET[filter]}.*" ${_DATA}/vcard/*vcf
74       ;;
75     course)
76       ;;
77     *) ls -1 ${_DATA}/vcard/*vcf 2>/dev/null
78       ;;
79   esac |case "${_GET[order]}" in
80     firstname)
81       while read file; do
82         fn=$(sed -rn 's:^N(;.+)*\:([^;]*;){1} *([^;]*).*$:\3:p' "$file")
83         echo "$fn\t$file"
84       done
85       ;;
86     lastname)
87       while read file; do
88         ln=$(sed -rn 's:^N(;.+)*\:([^;]*;){0} *([^;]*).*$:\3:p' "$file")
89         echo "$ln\t$file"
90       done
91       ;;
92     bdate)
93       while read file; do
94         bd=$(sed -rn 's:^BDAY(;.+)*\:(.*)$:\2:p' "$file")
95         echo "$bd\t$file"
96       done
97       ;;
98     *)
99       sed -r 's:^.*$:x\t&:'
100       ;;
101   esac |sort |sed -r 's:^.*\t(.*/)([^/]+)$:\2:'
102 }
103
104 vcf_parse() {
105   unset key
106   sed -r ':X;N;$!bX; s;\r\n[ \t];;g; s;\r\n;\n;g;' "$1" \
107   | sed -rn '
108     # === turn property names to upper case, strip group names ===
109     h; s;^([^;:]+);;;
110     x; s;^([^;:\.]+\.)?([^;:]+).*$;\2;;
111     y;abcdefghijklmnopqrstuvwxyz;ABCDEFGHIJKLMNOPQRSTUVWXYZ;
112     G; s;\n;;;
113
114     
115
116     # === Normalise various known vendor properties ===
117                 s;^X-MS-CARDPICTURE(\;|:);PHOTO\1;;
118                         s;^X-GENDER(\;|:);GENDER\1;;
119                    s;^X-ANNIVERSARY(\;|:);ANNIVERSARY\1;;
120          s;^X-EVOLUTION-ANNIVERSARY(\;|:);ANNIVERSARY\1;;
121     s;^X-KADDRESSBOOK-X-ANNIVERSARY(\;|:);ANNIVERSARY\1;;
122             s;^X-EVOLUTION-BLOG-URL(\;|:);URL\1;;
123                               s;^AGENT(\;|:);RELATED\;VALUE=text\;TYPE=agent\1;;
124                         s;^X-ASSISTANT(\;|:);RELATED\;VALUE=text\;TYPE=assistant\1;;
125               s;^X-EVOLUTION-ASSISTANT(\;|:);RELATED\;VALUE=text\;TYPE=assistant\1;;
126     s;^X-KADDRESSBOOK-X-ASSISTANTSNAME(\;|:);RELATED\;VALUE=text\;TYPE=assistant\1;;
127                           s;^X-MANAGER(\;|:);RELATED\;VALUE=text\;TYPE=manager\1;;
128                 s;^X-EVOLUTION-MANAGER(\;|:);RELATED\;VALUE=text\;TYPE=manager\1;;
129       s;^X-KADDRESSBOOK-X-MANAGERSNAME(\;|:);RELATED\;VALUE=text\;TYPE=manager\1;;
130                            s;^X-SPOUSE(\;|:);RELATED\;VALUE=text\;TYPE=spouse\1;;
131                  s;^X-EVOLUTION-SPOUSE(\;|:);RELATED\;VALUE=text\;TYPE=spouse\1;;
132         s;^X-KADDRESSBOOK-X-SPOUSENAME(\;|:);RELATED\;VALUE=text\;TYPE=spouse\1;;
133
134     # === Normalise obsolete vendor IM properties ===
135             s;^X-AIM((\;[a-Z0-9-]+|\;[a-Z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;IMPP\1:aim:;;
136             s;^X-ICQ((\;[a-Z0-9-]+|\;[a-Z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;IMPP\1:aim:;;
137     s;^X-GOOGLE-TALK((\;[a-Z0-9-]+|\;[a-Z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;IMPP\1:xmpp:;;
138          s;^X-JABBER((\;[a-Z0-9-]+|\;[a-Z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;IMPP\1:xmpp:;;
139             s;^X-MSN((\;[a-Z0-9-]+|\;[a-Z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;IMPP\1:msn:;;
140           s;^X-YAHOO((\;[a-Z0-9-]+|\;[a-Z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;IMPP\1:ymsgr:;;
141             s;^X-SIP((\;[a-Z0-9-]+|\;[a-Z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(sip:)?;IMPP\1:sip:;;
142
143     # === Update obsolete LABEL property ===
144     s;^LABEL((\;[a-Z0-9-]+|\;[a-Z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(.*)$;ADR\1\;LABEL="\5":;;
145
146     /^([A-Z0-9-]+)((\;[a-Z0-9-]+=?|\;[a-Z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(.*)$/{
147       h;
148       s;^([A-Z0-9-]+)((\;[a-Z0-9-]+=?|\;[a-Z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(.*)$;key='\''\1'\'';;
149       H; g;
150
151       s;^([A-Z0-9-]+)((\;[a-Z0-9-]+=?|\;[a-Z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):([^\n]*)\n.*$;\2;;
152       s;'\'';'\'\\\\\'\'';g
153       s;\;([A-Z0-9-]+)(=|=(([^\;,:"]+|"[^"]+")(,[^\;,:"]+|,"[^"]+")*))?;\ntag[\1]='\''\3'\'';g
154       s;^\n+;;; s;\n+$;;;
155       /^.+$/H; g;
156
157       s;^([A-Z0-9-]+)((\;[a-Z0-9-]+=?|\;[a-Z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):([^\n]*)\n.*$;\6;;
158       # :X s;((^|[^\\])(\\\\)*)[;,];\1\n;g; s;((^|[^\\])(\\\\)*)\\([;,]);\4;g; tX;
159       s;'\'';'\'\\\\\'\'';g
160       s;^.*$;value='\''&'\'';
161       H; g;
162
163       s;^[^\n]*[\n ]+;;
164       s;\n+$;;;
165
166       p;
167     }
168     ' \
169   | while read -r line; do
170     declare -A tag
171     case "$line" in
172       value*) eval "$line";;
173       tag*)   eval "$line";;
174       key*)
175         if [ -z "$key" ]; then
176           eval "$line"
177         else
178           values[${key}]="${value//\\r\\n/$BR}"
179           for t in ${(k)tag}; do
180             values[${key}_${t}]="${tag[$t]}"
181           done
182           eval "$line"
183           if [ -n "$values[$key]" ]; then
184             n=0
185             while [ -n "$values[$key$n]" ]; do n=$(($n + 1)); done
186             key=$key$n
187           fi
188           unset value
189           unset tag
190         fi
191       ;;
192     esac
193   done
194 }
195
196 view_card() {  #Parameter: Cardfile
197   id="$1"
198   cardfile="$_DATA/vcard/${id}"
199   cachefile="$_DATA/cache/${id}.cache"
200   if [ "$cachefile" -nt "$cardfile" ]; then
201     cat "$cachefile"
202   else
203     declare -A values
204     vcf_parse "$cardfile"
205     . $view_card |tee "$cachefile"
206   fi
207 }
208
209 edit_card() {  #Parameter: Cardfile
210   id="$1"
211   cardfile="$_DATA/vcard/$id"
212   tempfile="$_DATA/temp/$id"
213   [ -f "$tempfile" ] && cardfile="$tempfile"
214
215   declare -A values
216   vcf_parse "$cardfile"
217   . $edit_card
218 }