| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
2
|
|
|
|
|
|
|
# File: VCard.pm |
|
3
|
|
|
|
|
|
|
# |
|
4
|
|
|
|
|
|
|
# Description: Read vCard and iCalendar meta information |
|
5
|
|
|
|
|
|
|
# |
|
6
|
|
|
|
|
|
|
# Revisions: 2015/04/05 - P. Harvey Created |
|
7
|
|
|
|
|
|
|
# 2015/05/02 - PH Added iCalendar support |
|
8
|
|
|
|
|
|
|
# |
|
9
|
|
|
|
|
|
|
# References: 1) http://en.m.wikipedia.org/wiki/VCard |
|
10
|
|
|
|
|
|
|
# 2) http://tools.ietf.org/html/rfc6350 |
|
11
|
|
|
|
|
|
|
# 3) http://tools.ietf.org/html/rfc5545 |
|
12
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
package Image::ExifTool::VCard; |
|
15
|
|
|
|
|
|
|
|
|
16
|
1
|
|
|
1
|
|
3610
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
27
|
|
|
17
|
1
|
|
|
1
|
|
5
|
use vars qw($VERSION); |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
33
|
|
|
18
|
1
|
|
|
1
|
|
5
|
use Image::ExifTool qw(:DataAccess :Utils); |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
1953
|
|
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
$VERSION = '1.06'; |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
my %unescapeVCard = ( '\\'=>'\\', ','=>',', 'n'=>"\n", 'N'=>"\n" ); |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
# lookup for iCalendar components (used to generate family 1 group names if top level) |
|
25
|
|
|
|
|
|
|
my %isComponent = ( Event=>1, Todo=>1, Journal=>1, Freebusy=>1, Timezone=>1, Alarm=>1 ); |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
my %timeInfo = ( |
|
28
|
|
|
|
|
|
|
# convert common date/time formats to EXIF style |
|
29
|
|
|
|
|
|
|
ValueConv => q{ |
|
30
|
|
|
|
|
|
|
$val =~ s/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z?)/$1:$2:$3 $4:$5:$6$7/g; |
|
31
|
|
|
|
|
|
|
$val =~ s/(\d{4})(\d{2})(\d{2})/$1:$2:$3/g; |
|
32
|
|
|
|
|
|
|
$val =~ s/(\d{4})-(\d{2})-(\d{2})/$1:$2:$3/g; |
|
33
|
|
|
|
|
|
|
return $val; |
|
34
|
|
|
|
|
|
|
}, |
|
35
|
|
|
|
|
|
|
PrintConv => '$self->ConvertDateTime($val)', |
|
36
|
|
|
|
|
|
|
); |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
# vCard tags (ref 1/2/PH) |
|
39
|
|
|
|
|
|
|
# Note: The case of all tag ID's is normalized to lowercase with uppercase first letter |
|
40
|
|
|
|
|
|
|
%Image::ExifTool::VCard::Main = ( |
|
41
|
|
|
|
|
|
|
GROUPS => { 2 => 'Document' }, |
|
42
|
|
|
|
|
|
|
VARS => { NO_LOOKUP => 1 }, # omit tags from lookup |
|
43
|
|
|
|
|
|
|
NOTES => q{ |
|
44
|
|
|
|
|
|
|
This table lists common vCard tags, but ExifTool will also extract any other |
|
45
|
|
|
|
|
|
|
vCard tags found. Tag names may have "Pref" added to indicate the preferred |
|
46
|
|
|
|
|
|
|
instance of a vCard property, and other "TYPE" parameters may also added to |
|
47
|
|
|
|
|
|
|
the tag name. VCF files may contain multiple vCard entries which are |
|
48
|
|
|
|
|
|
|
distinguished by the ExifTool family 3 group name (document number). See |
|
49
|
|
|
|
|
|
|
L for the vCard 4.0 specification. |
|
50
|
|
|
|
|
|
|
}, |
|
51
|
|
|
|
|
|
|
Version => { Name => 'VCardVersion', Description => 'VCard Version' }, |
|
52
|
|
|
|
|
|
|
Fn => { Name => 'FormattedName', Groups => { 2 => 'Author' } }, |
|
53
|
|
|
|
|
|
|
N => { Name => 'Name', Groups => { 2 => 'Author' } }, |
|
54
|
|
|
|
|
|
|
Bday => { Name => 'Birthday', Groups => { 2 => 'Time' }, %timeInfo }, |
|
55
|
|
|
|
|
|
|
Tz => { Name => 'TimeZone', Groups => { 2 => 'Time' } }, |
|
56
|
|
|
|
|
|
|
Adr => { Name => 'Address', Groups => { 2 => 'Location' } }, |
|
57
|
|
|
|
|
|
|
Geo => { |
|
58
|
|
|
|
|
|
|
Name => 'Geolocation', |
|
59
|
|
|
|
|
|
|
Groups => { 2 => 'Location' }, |
|
60
|
|
|
|
|
|
|
# when used as a parameter, VCard 4.0 adds a "geo:" prefix that we need to remove |
|
61
|
|
|
|
|
|
|
ValueConv => '$val =~ s/^geo://; $val', |
|
62
|
|
|
|
|
|
|
}, |
|
63
|
|
|
|
|
|
|
Anniversary => { }, |
|
64
|
|
|
|
|
|
|
Email => { }, |
|
65
|
|
|
|
|
|
|
Gender => { }, |
|
66
|
|
|
|
|
|
|
Impp => 'IMPP', |
|
67
|
|
|
|
|
|
|
Lang => 'Language', |
|
68
|
|
|
|
|
|
|
Logo => { }, |
|
69
|
|
|
|
|
|
|
Nickname => { }, |
|
70
|
|
|
|
|
|
|
Note => { }, |
|
71
|
|
|
|
|
|
|
Org => 'Organization', |
|
72
|
|
|
|
|
|
|
Photo => { Groups => { 2 => 'Preview' } }, |
|
73
|
|
|
|
|
|
|
Prodid => 'Software', |
|
74
|
|
|
|
|
|
|
Rev => 'Revision', |
|
75
|
|
|
|
|
|
|
Sound => { }, |
|
76
|
|
|
|
|
|
|
Tel => 'Telephone', |
|
77
|
|
|
|
|
|
|
Title => 'JobTitle', |
|
78
|
|
|
|
|
|
|
Uid => 'UID', |
|
79
|
|
|
|
|
|
|
Url => 'URL', |
|
80
|
|
|
|
|
|
|
'X-ablabel' => { Name => 'ABLabel', PrintConv => '$val =~ s/^_\$!<(.*)>!\$_$/$1/; $val' }, |
|
81
|
|
|
|
|
|
|
'X-abdate' => { Name => 'ABDate', Groups => { 2 => 'Time' }, %timeInfo }, |
|
82
|
|
|
|
|
|
|
'X-aim' => 'AIM', |
|
83
|
|
|
|
|
|
|
'X-icq' => 'ICQ', |
|
84
|
|
|
|
|
|
|
'X-abuid' => 'AB_UID', |
|
85
|
|
|
|
|
|
|
'X-abrelatednames' => 'ABRelatedNames', |
|
86
|
|
|
|
|
|
|
'X-socialprofile' => 'SocialProfile', |
|
87
|
|
|
|
|
|
|
); |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
%Image::ExifTool::VCard::VCalendar = ( |
|
90
|
|
|
|
|
|
|
GROUPS => { 1 => 'VCalendar', 2 => 'Document' }, |
|
91
|
|
|
|
|
|
|
VARS => { |
|
92
|
|
|
|
|
|
|
NO_LOOKUP => 1, # omit tags from lookup |
|
93
|
|
|
|
|
|
|
LONG_TAGS => 6, # some X-microsoft tags have unavoidably long ID's |
|
94
|
|
|
|
|
|
|
}, |
|
95
|
|
|
|
|
|
|
NOTES => q{ |
|
96
|
|
|
|
|
|
|
The VCard module is also used to process iCalendar ICS files since they use |
|
97
|
|
|
|
|
|
|
a format similar to vCard. The following table lists standard iCalendar |
|
98
|
|
|
|
|
|
|
tags, but any existing tags will be extracted. Top-level iCalendar |
|
99
|
|
|
|
|
|
|
components (eg. Event, Todo, Timezone, etc.) are used for the family 1 group |
|
100
|
|
|
|
|
|
|
names, and embedded components (eg. Alarm) are added as a prefix to the tag |
|
101
|
|
|
|
|
|
|
name. See L for the official iCalendar |
|
102
|
|
|
|
|
|
|
2.0 specification. |
|
103
|
|
|
|
|
|
|
}, |
|
104
|
|
|
|
|
|
|
Version => { Name => 'VCalendarVersion', Description => 'VCalendar Version' }, |
|
105
|
|
|
|
|
|
|
Calscale => 'CalendarScale', |
|
106
|
|
|
|
|
|
|
Method => { }, |
|
107
|
|
|
|
|
|
|
Prodid => 'Software', |
|
108
|
|
|
|
|
|
|
Attach => 'Attachment', |
|
109
|
|
|
|
|
|
|
Categories => { }, |
|
110
|
|
|
|
|
|
|
Class => 'Classification', |
|
111
|
|
|
|
|
|
|
Comment => { }, |
|
112
|
|
|
|
|
|
|
Description => { }, |
|
113
|
|
|
|
|
|
|
Geo => { |
|
114
|
|
|
|
|
|
|
Name => 'Geolocation', |
|
115
|
|
|
|
|
|
|
Groups => { 2 => 'Location' }, |
|
116
|
|
|
|
|
|
|
ValueConv => '$val =~ s/^geo://; $val', |
|
117
|
|
|
|
|
|
|
}, |
|
118
|
|
|
|
|
|
|
Location => { Name => 'Location', Groups => { 2 => 'Location' } }, |
|
119
|
|
|
|
|
|
|
'Percent-complete' => 'PercentComplete', |
|
120
|
|
|
|
|
|
|
Priority => { }, |
|
121
|
|
|
|
|
|
|
Resources => { }, |
|
122
|
|
|
|
|
|
|
Status => { }, |
|
123
|
|
|
|
|
|
|
Summary => { }, |
|
124
|
|
|
|
|
|
|
Completed => { Name => 'DateTimeCompleted', Groups => { 2 => 'Time' }, %timeInfo }, |
|
125
|
|
|
|
|
|
|
Dtend => { Name => 'DateTimeEnd', Groups => { 2 => 'Time' }, %timeInfo }, |
|
126
|
|
|
|
|
|
|
Due => { Name => 'DateTimeDue', Groups => { 2 => 'Time' }, %timeInfo }, |
|
127
|
|
|
|
|
|
|
Dtstart => { Name => 'DateTimeStart', Groups => { 2 => 'Time' }, %timeInfo }, |
|
128
|
|
|
|
|
|
|
Duration => { }, |
|
129
|
|
|
|
|
|
|
Freebusy => 'FreeBusyTime', |
|
130
|
|
|
|
|
|
|
Transp => 'TimeTransparency', |
|
131
|
|
|
|
|
|
|
Tzid => { Name => 'TimezoneID', Groups => { 2 => 'Time' } }, |
|
132
|
|
|
|
|
|
|
Tzname => { Name => 'TimezoneName', Groups => { 2 => 'Time' } }, |
|
133
|
|
|
|
|
|
|
Tzoffsetfrom=> { Name => 'TimezoneOffsetFrom', Groups => { 2 => 'Time' } }, |
|
134
|
|
|
|
|
|
|
Tzoffsetto => { Name => 'TimezoneOffsetTo', Groups => { 2 => 'Time' } }, |
|
135
|
|
|
|
|
|
|
Tzurl => { Name => 'TimeZoneURL', Groups => { 2 => 'Time' } }, |
|
136
|
|
|
|
|
|
|
Attendee => { }, |
|
137
|
|
|
|
|
|
|
Contact => { }, |
|
138
|
|
|
|
|
|
|
Organizer => { }, |
|
139
|
|
|
|
|
|
|
'Recurrence-id' => 'RecurrenceID', |
|
140
|
|
|
|
|
|
|
'Related-to' => 'RelatedTo', |
|
141
|
|
|
|
|
|
|
Url => 'URL', |
|
142
|
|
|
|
|
|
|
Uid => 'UID', |
|
143
|
|
|
|
|
|
|
Exdate => { Name => 'ExceptionDateTimes', Groups => { 2 => 'Time' }, %timeInfo }, |
|
144
|
|
|
|
|
|
|
Rdate => { Name => 'RecurrenceDateTimes', Groups => { 2 => 'Time' }, %timeInfo }, |
|
145
|
|
|
|
|
|
|
Rrule => { Name => 'RecurrenceRule', Groups => { 2 => 'Time' } }, |
|
146
|
|
|
|
|
|
|
Action => { }, |
|
147
|
|
|
|
|
|
|
Repeat => { }, |
|
148
|
|
|
|
|
|
|
Trigger => { }, |
|
149
|
|
|
|
|
|
|
Created => { Name => 'DateCreated', Groups => { 2 => 'Time' }, %timeInfo }, |
|
150
|
|
|
|
|
|
|
Dtstamp => { Name => 'DateTimeStamp', Groups => { 2 => 'Time' }, %timeInfo }, |
|
151
|
|
|
|
|
|
|
'Last-modified' => { Name => 'ModifyDate', Groups => { 2 => 'Time' }, %timeInfo }, |
|
152
|
|
|
|
|
|
|
Sequence => 'SequenceNumber', |
|
153
|
|
|
|
|
|
|
'Request-status' => 'RequestStatus', |
|
154
|
|
|
|
|
|
|
Acknowledged=> { Name => 'Acknowledged', Groups => { 2 => 'Time' }, %timeInfo }, |
|
155
|
|
|
|
|
|
|
# |
|
156
|
|
|
|
|
|
|
# Observed X-tags (not a comprehensive list): |
|
157
|
|
|
|
|
|
|
# |
|
158
|
|
|
|
|
|
|
'X-apple-calendar-color'=> 'CalendarColor', |
|
159
|
|
|
|
|
|
|
'X-apple-default-alarm' => 'DefaultAlarm', |
|
160
|
|
|
|
|
|
|
'X-apple-local-default-alarm' => 'LocalDefaultAlarm', |
|
161
|
|
|
|
|
|
|
'X-microsoft-cdo-appt-sequence' => 'AppointmentSequence', |
|
162
|
|
|
|
|
|
|
'X-microsoft-cdo-ownerapptid' => 'OwnerAppointmentID', |
|
163
|
|
|
|
|
|
|
'X-microsoft-cdo-busystatus' => 'BusyStatus', |
|
164
|
|
|
|
|
|
|
'X-microsoft-cdo-intendedstatus' => 'IntendedBusyStatus', |
|
165
|
|
|
|
|
|
|
'X-microsoft-cdo-alldayevent' => 'AllDayEvent', |
|
166
|
|
|
|
|
|
|
'X-microsoft-cdo-importance' => { |
|
167
|
|
|
|
|
|
|
Name => 'Importance', |
|
168
|
|
|
|
|
|
|
PrintConv => { |
|
169
|
|
|
|
|
|
|
0 => 'Low', |
|
170
|
|
|
|
|
|
|
1 => 'Normal', |
|
171
|
|
|
|
|
|
|
2 => 'High', |
|
172
|
|
|
|
|
|
|
}, |
|
173
|
|
|
|
|
|
|
}, |
|
174
|
|
|
|
|
|
|
'X-microsoft-cdo-insttype' => { |
|
175
|
|
|
|
|
|
|
Name => 'InstanceType', |
|
176
|
|
|
|
|
|
|
PrintConv => { |
|
177
|
|
|
|
|
|
|
0 => 'Non-recurring Appointment', |
|
178
|
|
|
|
|
|
|
1 => 'Recurring Appointment', |
|
179
|
|
|
|
|
|
|
2 => 'Single Instance of Recurring Appointment', |
|
180
|
|
|
|
|
|
|
3 => 'Exception to Recurring Appointment', |
|
181
|
|
|
|
|
|
|
}, |
|
182
|
|
|
|
|
|
|
}, |
|
183
|
|
|
|
|
|
|
'X-microsoft-donotforwardmeeting' => 'DoNotForwardMeeting', |
|
184
|
|
|
|
|
|
|
'X-microsoft-disallow-counter' => 'DisallowCounterProposal', |
|
185
|
|
|
|
|
|
|
'X-microsoft-locations' => { Name => 'MeetingLocations', Groups => { 2 => 'Location' } }, |
|
186
|
|
|
|
|
|
|
'X-wr-caldesc' => 'CalendarDescription', |
|
187
|
|
|
|
|
|
|
'X-wr-calname' => 'CalendarName', |
|
188
|
|
|
|
|
|
|
'X-wr-relcalid' => 'CalendarID', |
|
189
|
|
|
|
|
|
|
'X-wr-timezone' => { Name => 'TimeZone2', Groups => { 2 => 'Time' } }, |
|
190
|
|
|
|
|
|
|
'X-wr-alarmuid' => 'AlarmUID', |
|
191
|
|
|
|
|
|
|
); |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
194
|
|
|
|
|
|
|
# Get vCard tag, creating if necessary |
|
195
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) tag table ref, 2) tag ID, 3) tag Name, |
|
196
|
|
|
|
|
|
|
# 4) source tagInfo ref, 5) lang code |
|
197
|
|
|
|
|
|
|
# Returns: tagInfo ref |
|
198
|
|
|
|
|
|
|
sub GetVCardTag($$$$;$$) |
|
199
|
|
|
|
|
|
|
{ |
|
200
|
120
|
|
|
120
|
0
|
271
|
my ($et, $tagTablePtr, $tag, $name, $srcInfo, $langCode) = @_; |
|
201
|
120
|
|
|
|
|
181
|
my $tagInfo = $$tagTablePtr{$tag}; |
|
202
|
120
|
100
|
|
|
|
193
|
unless ($tagInfo) { |
|
203
|
52
|
100
|
|
|
|
75
|
if ($srcInfo) { |
|
204
|
50
|
|
|
|
|
203
|
$tagInfo = { %$srcInfo }; |
|
205
|
|
|
|
|
|
|
} else { |
|
206
|
2
|
|
|
|
|
5
|
$tagInfo = { }; |
|
207
|
2
|
|
|
|
|
14
|
$et->VPrint(0, $$et{INDENT}, "[adding $tag]\n"); |
|
208
|
|
|
|
|
|
|
} |
|
209
|
52
|
|
|
|
|
93
|
$$tagInfo{Name} = $name; |
|
210
|
52
|
|
|
|
|
61
|
delete $$tagInfo{Description}; # create new description |
|
211
|
52
|
|
|
|
|
103
|
AddTagToTable($tagTablePtr, $tag, $tagInfo); |
|
212
|
|
|
|
|
|
|
} |
|
213
|
|
|
|
|
|
|
# handle alternate languages (the "language" parameter) |
|
214
|
120
|
100
|
|
|
|
185
|
$tagInfo = Image::ExifTool::GetLangInfo($tagInfo, $langCode) if $langCode; |
|
215
|
120
|
|
|
|
|
187
|
return $tagInfo; |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
219
|
|
|
|
|
|
|
# Decode vCard text |
|
220
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) vCard text, 2) encoding |
|
221
|
|
|
|
|
|
|
# Returns: decoded text (or array ref for a list of values) |
|
222
|
|
|
|
|
|
|
sub DecodeVCardText($$;$) |
|
223
|
|
|
|
|
|
|
{ |
|
224
|
120
|
|
|
120
|
0
|
271
|
my ($et, $val, $enc) = @_; |
|
225
|
120
|
100
|
|
|
|
208
|
$enc = defined($enc) ? lc $enc : ''; |
|
226
|
120
|
100
|
100
|
|
|
346
|
if ($enc eq 'b' or $enc eq 'base64') { |
|
227
|
2
|
|
|
|
|
1044
|
require Image::ExifTool::XMP; |
|
228
|
2
|
|
|
|
|
69
|
$val = Image::ExifTool::XMP::DecodeBase64($val); |
|
229
|
|
|
|
|
|
|
} else { |
|
230
|
118
|
100
|
|
|
|
171
|
if ($enc eq 'quoted-printable') { |
|
231
|
|
|
|
|
|
|
# convert "=HH" hex codes to characters |
|
232
|
1
|
|
|
|
|
14
|
$val =~ s/=([0-9a-f]{2})/chr(hex($1))/ige; |
|
|
4
|
|
|
|
|
14
|
|
|
233
|
|
|
|
|
|
|
} |
|
234
|
118
|
|
|
|
|
274
|
$val = $et->Decode($val, 'UTF8'); # convert from UTF-8 |
|
235
|
|
|
|
|
|
|
# convert unescaped commas to nulls to separate list items |
|
236
|
118
|
100
|
|
|
|
397
|
$val =~ s/(\\.)|(,)/$1 || "\0"/sge; |
|
|
6
|
|
|
|
|
26
|
|
|
237
|
|
|
|
|
|
|
# unescape necessary characters in value |
|
238
|
118
|
50
|
|
|
|
164
|
$val =~ s/\\(.)/$unescapeVCard{$1}||$1/sge; |
|
|
5
|
|
|
|
|
17
|
|
|
239
|
|
|
|
|
|
|
# split into list if necessary |
|
240
|
118
|
|
|
|
|
260
|
my @vals = split /\0/, $val; |
|
241
|
118
|
100
|
|
|
|
240
|
$val = \@vals if @vals > 1; |
|
242
|
|
|
|
|
|
|
} |
|
243
|
120
|
|
|
|
|
195
|
return $val; |
|
244
|
|
|
|
|
|
|
} |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
247
|
|
|
|
|
|
|
# Read information in a vCard file |
|
248
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) dirInfo ref |
|
249
|
|
|
|
|
|
|
# Returns: 1 on success, 0 if this wasn't a valid vCard file |
|
250
|
|
|
|
|
|
|
sub ProcessVCard($$) |
|
251
|
|
|
|
|
|
|
{ |
|
252
|
2
|
|
|
2
|
0
|
3
|
local $_; |
|
253
|
2
|
|
|
|
|
7
|
my ($et, $dirInfo) = @_; |
|
254
|
2
|
|
|
|
|
4
|
my $raf = $$dirInfo{RAF}; |
|
255
|
2
|
|
|
|
|
6
|
my ($buff, $val, $ok, $component, %compNum, @count); |
|
256
|
|
|
|
|
|
|
|
|
257
|
2
|
50
|
33
|
|
|
5
|
return 0 unless $raf->Read($buff, 24) and $raf->Seek(0,0) and $buff=~/^BEGIN:(VCARD|VCALENDAR)\r\n/i; |
|
|
|
|
33
|
|
|
|
|
|
258
|
2
|
100
|
|
|
|
15
|
my ($type, $lbl, $tbl, $ext) = uc($1) eq 'VCARD' ? qw(VCard vCard Main VCF) : qw(ICS iCalendar VCalendar ICS); |
|
259
|
2
|
|
|
|
|
14
|
$et->SetFileType($type, undef, $ext); |
|
260
|
2
|
50
|
33
|
|
|
9
|
return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3; |
|
261
|
2
|
|
|
|
|
11
|
local $/ = "\r\n"; |
|
262
|
2
|
|
|
|
|
44
|
my $tagTablePtr = GetTagTable("Image::ExifTool::VCard::$tbl"); |
|
263
|
2
|
|
|
|
|
12
|
my $more = $raf->ReadLine($buff); # read first line |
|
264
|
2
|
50
|
|
|
|
9
|
chomp $buff if $more; |
|
265
|
2
|
|
|
|
|
4
|
while ($more) { |
|
266
|
|
|
|
|
|
|
# retrieve previous line from $buff |
|
267
|
139
|
50
|
|
|
|
264
|
$val = $buff if defined $buff; |
|
268
|
|
|
|
|
|
|
# read ahead to next line to see if is a continuation |
|
269
|
139
|
|
|
|
|
311
|
$more = $raf->ReadLine($buff); |
|
270
|
139
|
100
|
|
|
|
247
|
if ($more) { |
|
271
|
137
|
|
|
|
|
168
|
chomp $buff; |
|
272
|
|
|
|
|
|
|
# add continuation line if necessary |
|
273
|
137
|
50
|
|
|
|
337
|
$buff =~ s/^[ \t]// and $val .= $buff, undef($buff), next; |
|
274
|
|
|
|
|
|
|
} |
|
275
|
139
|
100
|
|
|
|
403
|
if ($val =~ /^(BEGIN|END):(V?)(\w+)$/i) { |
|
|
|
100
|
|
|
|
|
|
|
276
|
28
|
100
|
|
|
|
113
|
my ($begin, $v, $what) = ((lc($1) eq 'begin' ? 1 : 0), $2, ucfirst lc $3); |
|
277
|
28
|
100
|
100
|
|
|
99
|
if ($what eq 'Card' or $what eq 'Calendar') { |
|
278
|
6
|
100
|
|
|
|
11
|
if ($begin) { |
|
279
|
3
|
|
|
|
|
8
|
@count = ( { } ); # reset group counters |
|
280
|
|
|
|
|
|
|
} else { |
|
281
|
3
|
|
|
|
|
7
|
$ok = 1; # ok if we read at least on full VCARD or VCALENDAR |
|
282
|
|
|
|
|
|
|
} |
|
283
|
6
|
|
|
|
|
13
|
next; |
|
284
|
|
|
|
|
|
|
} |
|
285
|
|
|
|
|
|
|
# absorb top-level component into family 1 group name |
|
286
|
22
|
100
|
|
|
|
44
|
if ($isComponent{$what}) { |
|
287
|
14
|
100
|
66
|
|
|
35
|
if ($begin) { |
|
|
|
100
|
|
|
|
|
|
|
288
|
7
|
100
|
|
|
|
12
|
unless ($component) { |
|
289
|
|
|
|
|
|
|
# begin a new top-level component |
|
290
|
4
|
|
|
|
|
10
|
@count = ( { } ); |
|
291
|
4
|
|
|
|
|
8
|
$component = $what; |
|
292
|
4
|
|
100
|
|
|
14
|
$compNum{$component} = ($compNum{$component} || 0) + 1; |
|
293
|
4
|
|
|
|
|
9
|
next; |
|
294
|
|
|
|
|
|
|
} |
|
295
|
|
|
|
|
|
|
} elsif ($component and $component eq $what) { |
|
296
|
|
|
|
|
|
|
# this top-level component has ended |
|
297
|
4
|
|
|
|
|
7
|
undef $component; |
|
298
|
4
|
|
|
|
|
8
|
next; |
|
299
|
|
|
|
|
|
|
} |
|
300
|
|
|
|
|
|
|
} |
|
301
|
|
|
|
|
|
|
# keep count of each component at this level |
|
302
|
14
|
100
|
|
|
|
42
|
if ($begin) { |
|
|
|
50
|
|
|
|
|
|
|
303
|
7
|
100
|
100
|
|
|
20
|
$count[-1]{$what} = ($count[-1]{$what} || 0) + 1 if $v; |
|
304
|
7
|
|
|
|
|
18
|
push @count, { obj => $what }; |
|
305
|
|
|
|
|
|
|
} elsif (@count > 1) { |
|
306
|
7
|
|
|
|
|
10
|
pop @count; |
|
307
|
|
|
|
|
|
|
} |
|
308
|
14
|
|
|
|
|
32
|
next; |
|
309
|
|
|
|
|
|
|
} elsif ($ok) { |
|
310
|
1
|
|
|
|
|
2
|
$ok = 0; |
|
311
|
1
|
|
|
|
|
7
|
$$et{DOC_NUM} = ++$$et{DOC_COUNT}; # read next card as a new document |
|
312
|
|
|
|
|
|
|
} |
|
313
|
111
|
50
|
|
|
|
410
|
unless ($val =~ s/^([-A-Za-z0-9.]+)//) { |
|
314
|
0
|
|
|
|
|
0
|
$et->WarnOnce("Unrecognized line in $lbl file"); |
|
315
|
0
|
|
|
|
|
0
|
next; |
|
316
|
|
|
|
|
|
|
} |
|
317
|
111
|
|
|
|
|
247
|
my $tag = $1; |
|
318
|
|
|
|
|
|
|
# set group if it exists |
|
319
|
111
|
100
|
|
|
|
231
|
if ($tag =~ s/^([-A-Za-z0-9]+)\.//) { |
|
|
|
100
|
|
|
|
|
|
|
320
|
8
|
|
|
|
|
21
|
$$et{SET_GROUP1} = ucfirst lc $1; |
|
321
|
|
|
|
|
|
|
} elsif ($component) { |
|
322
|
64
|
|
|
|
|
153
|
$$et{SET_GROUP1} = $component . $compNum{$component}; |
|
323
|
|
|
|
|
|
|
} else { |
|
324
|
39
|
|
|
|
|
53
|
delete $$et{SET_GROUP1}; |
|
325
|
|
|
|
|
|
|
} |
|
326
|
111
|
|
|
|
|
150
|
my ($name, %param, $p); |
|
327
|
|
|
|
|
|
|
# vCard tag ID's are case-insensitive, so normalize to lowercase with |
|
328
|
|
|
|
|
|
|
# an uppercase first letter for use as a tag name |
|
329
|
111
|
100
|
|
|
|
233
|
$name = ucfirst $tag if $tag =~ /[a-z]/; # preserve mixed case in name if it exists |
|
330
|
111
|
|
|
|
|
174
|
$tag = ucfirst lc $tag; |
|
331
|
|
|
|
|
|
|
# get source tagInfo reference |
|
332
|
111
|
|
|
|
|
230
|
my $srcInfo = $et->GetTagInfo($tagTablePtr, $tag); |
|
333
|
111
|
100
|
|
|
|
212
|
if ($srcInfo) { |
|
334
|
110
|
|
|
|
|
170
|
$name = $$srcInfo{Name}; # use our name |
|
335
|
|
|
|
|
|
|
} else { |
|
336
|
1
|
50
|
|
|
|
3
|
$name or $name = $tag; |
|
337
|
|
|
|
|
|
|
# remove leading "X-" from name if it exists |
|
338
|
1
|
50
|
|
|
|
4
|
$name =~ s/^X-// and $name = ucfirst $name; |
|
339
|
|
|
|
|
|
|
} |
|
340
|
|
|
|
|
|
|
# add object name(s) to tag if necessary |
|
341
|
111
|
100
|
|
|
|
193
|
if (@count > 1) { |
|
342
|
40
|
|
|
|
|
42
|
my $i; |
|
343
|
40
|
|
|
|
|
84
|
for ($i=$#count-1; $i>=0; --$i) { |
|
344
|
40
|
|
|
|
|
66
|
my $pre = $count[$i-1]{obj}; # use containing object name as tag prefix |
|
345
|
40
|
|
|
|
|
52
|
my $c = $count[$i]{$pre}; # add index for object number |
|
346
|
40
|
100
|
|
|
|
71
|
$c = '' unless defined $c; |
|
347
|
40
|
|
|
|
|
96
|
$tag = $pre . $c . $tag; |
|
348
|
40
|
|
|
|
|
88
|
$name = $pre . $c . $name; |
|
349
|
|
|
|
|
|
|
} |
|
350
|
|
|
|
|
|
|
} |
|
351
|
|
|
|
|
|
|
# parse parameters |
|
352
|
111
|
|
|
|
|
298
|
while ($val =~ s/^;([-A-Za-z0-9]*)(=?)//) { |
|
353
|
56
|
|
|
|
|
110
|
$p = ucfirst lc $1; |
|
354
|
|
|
|
|
|
|
# convert old vCard 2.x parameters to the new "TYPE=" format |
|
355
|
56
|
100
|
|
|
|
107
|
$2 or $val = $1 . $val, $p = 'Type'; |
|
356
|
|
|
|
|
|
|
# read parameter value |
|
357
|
56
|
|
|
|
|
65
|
for (;;) { |
|
358
|
112
|
100
|
100
|
|
|
434
|
last unless $val =~ s/^"([^"]*)",?// or $val =~ s/^([^";:,]+,?)//; |
|
359
|
56
|
100
|
|
|
|
131
|
my $v = $p eq 'Type' ? ucfirst lc $1 : $1; |
|
360
|
56
|
100
|
|
|
|
134
|
$param{$p} = defined($param{$p}) ? $param{$p} . $v : $v; |
|
361
|
|
|
|
|
|
|
} |
|
362
|
56
|
50
|
|
|
|
87
|
if (defined $param{$p}) { |
|
363
|
56
|
50
|
|
|
|
142
|
$param{$p} =~ s/\\(.)/$unescapeVCard{$1}||$1/sge; |
|
|
1
|
|
|
|
|
7
|
|
|
364
|
|
|
|
|
|
|
} else { |
|
365
|
0
|
|
|
|
|
0
|
$param{$p} = ''; |
|
366
|
|
|
|
|
|
|
} |
|
367
|
|
|
|
|
|
|
} |
|
368
|
111
|
50
|
|
|
|
327
|
$val =~ s/^:// or $et->WarnOnce("Invalid line in $lbl file"), next; |
|
369
|
|
|
|
|
|
|
# add 'Type' parameter to id and name if it exists |
|
370
|
111
|
100
|
|
|
|
219
|
$param{Type} and $tag .= $param{Type}, $name .= $param{Type}; |
|
371
|
|
|
|
|
|
|
# convert base64-encoded data |
|
372
|
111
|
100
|
|
|
|
198
|
if ($val =~ s{^data:(\w+)/(\w+);base64,}{}) { |
|
373
|
1
|
|
|
|
|
5
|
my $xtra = ucfirst(lc $1) . ucfirst(lc $2); |
|
374
|
1
|
|
|
|
|
4
|
$tag .= $xtra; |
|
375
|
1
|
|
|
|
|
2
|
$name .= $xtra; |
|
376
|
1
|
|
|
|
|
2
|
$param{Encoding} = 'base64'; |
|
377
|
|
|
|
|
|
|
} |
|
378
|
111
|
|
|
|
|
266
|
$val = DecodeVCardText($et, $val, $param{Encoding}); |
|
379
|
111
|
|
|
|
|
282
|
my $tagInfo = GetVCardTag($et, $tagTablePtr, $tag, $name, $srcInfo, $param{Language}); |
|
380
|
111
|
|
|
|
|
320
|
$et->HandleTag($tagTablePtr, $tag, $val, TagInfo => $tagInfo); |
|
381
|
|
|
|
|
|
|
# handle some other parameters that we care about (ignore the rest for now) |
|
382
|
111
|
|
|
|
|
161
|
foreach $p (qw(Geo Label Tzid)) { |
|
383
|
333
|
100
|
|
|
|
658
|
next unless defined $param{$p}; |
|
384
|
|
|
|
|
|
|
# use tag attributes from our table if it exists |
|
385
|
9
|
|
|
|
|
28
|
my $srcTag2 = $et->GetTagInfo($tagTablePtr, $p); |
|
386
|
9
|
100
|
|
|
|
21
|
my $pn = $srcTag2 ? $$srcTag2{Name} : $p; |
|
387
|
9
|
|
|
|
|
24
|
$val = DecodeVCardText($et, $param{$p}); |
|
388
|
|
|
|
|
|
|
# add parameter to tag ID and name |
|
389
|
9
|
|
|
|
|
26
|
my ($tg, $nm) = ($tag . $p, $name . $pn); |
|
390
|
9
|
|
|
|
|
22
|
$tagInfo = GetVCardTag($et, $tagTablePtr, $tg, $nm, $srcTag2, $param{Language}); |
|
391
|
9
|
|
|
|
|
28
|
$et->HandleTag($tagTablePtr, $tg, $val, TagInfo => $tagInfo); |
|
392
|
|
|
|
|
|
|
} |
|
393
|
|
|
|
|
|
|
} |
|
394
|
2
|
|
|
|
|
5
|
delete $$et{SET_GROUP1}; |
|
395
|
2
|
|
|
|
|
3
|
delete $$et{DOC_NUM}; |
|
396
|
2
|
50
|
|
|
|
6
|
$ok or $et->Warn("Missing $lbl end"); |
|
397
|
2
|
|
|
|
|
15
|
return 1; |
|
398
|
|
|
|
|
|
|
} |
|
399
|
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
1; # end |
|
401
|
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
__END__ |