slightly quicker pdi loading
[confetti] / 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 pdi_load() {
30   sed -En '
31     # === Read entire file into buffer ===
32     :X $bY; N; bX; :Y s;^.*$;\n&\n;;
33
34     # === Join continuing lines, strip trailing CRs ===
35     s;\r\n[ \t];;g;
36     s;\r\n;\n;g;
37
38     # === turn property names to upper case, strip group names ===
39     s;\n([^;:\.\n]+\.)([^;:\n]+);\n\2;g;
40     :upcase
41     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;
42     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;
43     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;
44     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;
45     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;
46     s;(\n[^;:]*)z;\1Z;g;
47     t upcase;
48
49     # === Insert empty attribute fields where no attributes are present ===
50     s;\n([^;:]+):;\n\1\;:;g;
51
52     # === Unscramble aggregated fields ===
53     :disag
54     s;\n([^:]+:)((.*[^\])?(\\\\)*),;\n\1\2\n\1;;
55     t disag;
56
57     # === Insert FN when only N is present ===
58     /\nFN[;:]/!{
59       s,\nN(;[^:]*)?:([^;\n]*);([^;\n]*);([^;\n]*);([^;\n]*);([^;\n]*);?\n,&FN;:\5 \3 \4 \2 \6\n,;
60       :despace
61       s,(\nFN;:[^\n]*)  ([^\n]*\n),\1 \2,;
62       s,(\nFN;:) ([^\n]*\n),\1\2,;
63       s,(\nFN;:[^\n]*) (\n),\1\2,;
64       t despace;
65     }
66     /\nFN[;:]/!{ s,\n(N[;:][^\n]*)\n,&F\1\n,; }  # Fallback
67
68     # === Normalise various known vendor properties ===
69                 s;\nX-MS-CARDPICTURE(\;|:);\nPHOTO\1;g;
70                         s;\nX-GENDER(\;|:);\nGENDER\1;g;
71                    s;\nX-ANNIVERSARY(\;|:);\nANNIVERSARY\1;g;
72          s;\nX-EVOLUTION-ANNIVERSARY(\;|:);\nANNIVERSARY\1;g;
73     s;\nX-KADDRESSBOOK-X-ANNIVERSARY(\;|:);\nANNIVERSARY\1;g;
74             s;\nX-EVOLUTION-BLOG-URL(\;|:);\nURL\1;g;
75                            s;\nAGENT(\;|:);\nRELATED\;VALUE=text\;TYPE=agent\1;g;
76                      s;\nX-ASSISTANT(\;|:);\nRELATED\;VALUE=text\;TYPE=assistant\1;g;
77            s;\nX-EVOLUTION-ASSISTANT(\;|:);\nRELATED\;VALUE=text\;TYPE=assistant\1;g;
78  s;\nX-KADDRESSBOOK-X-ASSISTANTSNAME(\;|:);\nRELATED\;VALUE=text\;TYPE=assistant\1;g;
79                        s;\nX-MANAGER(\;|:);\nRELATED\;VALUE=text\;TYPE=manager\1;g;
80              s;\nX-EVOLUTION-MANAGER(\;|:);\nRELATED\;VALUE=text\;TYPE=manager\1;g;
81    s;\nX-KADDRESSBOOK-X-MANAGERSNAME(\;|:);\nRELATED\;VALUE=text\;TYPE=manager\1;g;
82                         s;\nX-SPOUSE(\;|:);\nRELATED\;VALUE=text\;TYPE=spouse\1;g;
83               s;\nX-EVOLUTION-SPOUSE(\;|:);\nRELATED\;VALUE=text\;TYPE=spouse\1;g;
84      s;\nX-KADDRESSBOOK-X-SPOUSENAME(\;|:);\nRELATED\;VALUE=text\;TYPE=spouse\1;g;
85
86     # === Normalise obsolete vendor IM properties ===
87             s;\nX-AIM((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:aim:;g;
88             s;\nX-ICQ((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:aim:;g;
89     s;\nX-GOOGLE-TALK((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:xmpp:;g;
90          s;\nX-JABBER((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:xmpp:;g;
91             s;\nX-MSN((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:msn:;g;
92           s;\nX-YAHOO((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:ymsgr:;g;
93             s;\nX-SIP((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(sip:)?;\nIMPP\1:sip:;g;
94
95     # === Update obsolete LABEL property ===
96     s;\nLABEL((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(.*)\n;\nADR\1\;LABEL="\5":\n;g;
97
98     p;' "$1"
99 }
100
101 pdi_count(){
102   local card="$1" name="$2" rc='' cnt=0
103   while rc="${card#*${BR}${name};}"; do
104     [ "${rc}" != "${card}" ] || break
105     card="$rc"
106     cnt=$(($cnt + 1))
107   done
108   printf %i\\n $cnt
109 }
110
111 pdi_attrib(){
112   local card=":$1" name="$2" cnt="${3:-1}"
113   while [ $cnt -gt 0 ]; do
114     [ "${card#*${BR}${name};}" = "$card" ] && return 1
115     card="${card#*${BR}${name};}"
116     cnt=$((cnt - 1))
117   done
118   printf %s\\n "${card%%:*}"
119 }
120
121 pdi_value(){
122   local card="${BR}$1" name="$2" cnt="${3:-1}"
123   while [ $cnt -gt 0 ]; do
124     [ "${card#*${BR}${name};*:}" = "$card" ] && return 1
125     card="${card#*${BR}${name};*:}"
126     cnt=$((cnt - 1))
127   done
128   printf %s\\n "${card%%${BR}*}"
129 }