53
# Various crashes have been encountered in the Perl interpreter
54
# executable when enabling UTF-8. It is only needed for nicer
55
# side-by-side comparison of changes (correct column width),
56
# so not much functionality is lost by disabling this.
57
# use encoding 'utf8';
59
# Instead enable writing the result as UTF-8. Input
60
# files are read as UTF-8 via PerlIO parameters in open().
61
binmode(STDOUT, ":utf8");
53
63
use Algorithm::Diff;
55
65
# ignore differences caused by specific servers or local backends?
104
119
return join("", @res);
107
# parameters: text, width to use for reformatted lines
108
# returns list of lines without line breaks
117
foreach $_ ( split( /(?:(?<=\nEND:VCARD)|(?<=\nEND:VCALENDAR))\n*/ ) ) {
122
# normalize the DATE-TIME duration unless the VALUE isn't a duration
123
sub NormalizeTrigger {
125
$value =~ s/([+-]?)P(?:(\d*)D)?T(?:(\d*)H)?(?:(\d*)M)?(?:(\d*)S)?/$1 .
126
"P" . (int($2) ? ($2 . "D") : "") . "T" .
127
(int($3) ? ($3 . "H") : "") .
128
(int($4) ? ($4 . "M") : "") .
129
(int($5) ? ($5 . "S") : "")/e;
133
# called for one VCALENDAR (with single VEVENT/VTODO/VJOURNAL) or VCARD,
134
# returns normalized one
118
139
# undo line continuation
120
141
# ignore charset specifications, assume UTF-8
125
146
# meeting invitations/replies correctly
126
147
s/((VCARD|VJOURNAL).*)^UID:[^\n]*\n/$1/msg;
149
# intentional changes to UID are acceptable when running with CLIENT_TEST_UNIQUE_UID
151
s/UID:UNIQUE-UID-\d+-/UID:/g;
154
# merge all CATEGORIES properties into one comma-separated one
155
while ( s/^CATEGORIES:([^\n]*)\n(.*)^CATEGORIES:([^\n]*)\n/CATEGORIES:$1,$3\n$2/ms ) {}
128
157
# exact order of categories is irrelevant
129
158
s/^CATEGORIES:(\S+)/"CATEGORIES:" . sortlist($1)/mge;
198
230
s/^((ATTENDEE|ORGANIZER).*):[Mm][Aa][Ii][Ll][Tt][Oo]:/$1:mailto:/mg;
200
232
# remove fields which may differ
201
s/^(PRODID|CREATED|DTSTAMP|LAST-MODIFIED|REV):.*\r?\n?//gm;
233
s/^(PRODID|CREATED|DTSTAMP|LAST-MODIFIED|REV)(;X-VOBJ-FLOATINGTIME-ALLOWED=(TRUE|FALSE))?:.*\r?\n?//gm;
202
234
# remove optional fields
203
s/^(METHOD|X-WSS-[A-Z]*):.*\r?\n?//gm;
235
s/^(METHOD|X-WSS-[A-Z]*|X-WR-[A-Z]*|CALSCALE):.*\r?\n?//gm;
205
237
# trailing line break(s) in a DESCRIPTION may or may not be
206
238
# removed or added by servers
222
259
s/^SUMMARY:(.*)$/SUMMARY:$1\nDESCRIPTION:$1/m;
225
# Strip trailing digits from TZID. They are appended by
226
# Evolution and SyncEvolution to distinguish VTIMEZONE
227
# definitions which have the same TZID, but different rules.
228
s/(^TZID:|;TZID=)([^;:]*?) \d+/$1$2/gm;
230
# Strip trailing -(Standard) from TZID. Evolution 2.24.5 adds
231
# that (not sure exactly where that comes from).
232
s/(^TZID:|;TZID=)([^;:]*?)-\(Standard\)/$1$2/gm;
234
# VTIMEZONE and TZID do not have to be preserved verbatim as long
235
# as the replacement is still representing the same timezone.
236
# Reduce TZIDs which follow the Olson database pseudo-standard
237
# to their location part and strip the VTIMEZONE - makes the
239
s;^BEGIN:VTIMEZONE.*?^TZID:/[^/\n]*/[^/\n]*/(\S+).*^END:VTIMEZONE;BEGIN:VTIMEZONE\nTZID:$1 [...]\nEND:VTIMEZONE;gms;
240
s;TZID=/[^/\n]*/[^/\n]*/(.*)$;TZID=$1;gm;
262
# strip configurable X- parameters or properties
263
my $strip = $ENV{CLIENT_TEST_STRIP_PROPERTIES};
265
s/^$strip(;[^:;\n]*)*:.*\r?\n?//gm;
267
$strip = $ENV{CLIENT_TEST_STRIP_PARAMETERS};
269
while (s/^(\w+)([^:\n]*);$strip=\d+/$1$2/mg) {}
272
if (!$full_timezones) {
273
# Strip trailing digits from TZID. They are appended by
274
# Evolution and SyncEvolution to distinguish VTIMEZONE
275
# definitions which have the same TZID, but different rules.
276
s/(^TZID:|;TZID=)([^;:]*?) \d+/$1$2/gm;
278
# Strip trailing -(Standard) from TZID. Evolution 2.24.5 adds
279
# that (not sure exactly where that comes from).
280
s/(^TZID:|;TZID=)([^;:]*?)-\(Standard\)/$1$2/gm;
282
# VTIMEZONE and TZID do not have to be preserved verbatim as long
283
# as the replacement is still representing the same timezone.
284
# Reduce TZIDs which specify a proper location
285
# to their location part and strip the VTIMEZONE - makes the
287
my $location = "[^\n]*((?:Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Brazil|Canada|Chile|Egypt|Eire|Europe|Hongkong|Iceland|India|Iran|Israel|Jamaica|Japan|Kwajalein|Libya|Mexico|Mideast|Navajo|Pacific|Poland|Portugal|Singapore|Turkey|Zulu)[-a-zA-Z0-9_/]*)";
288
s;^BEGIN:VTIMEZONE.*?^TZID:$location.*^END:VTIMEZONE;BEGIN:VTIMEZONE\n TZID:$1 [...]\nEND:VTIMEZONE;gms;
289
s;TZID="?$location"?;TZID=$1;gm;
242
292
# normalize iCalendar 2.0
243
293
if (/^BEGIN:(VEVENT|VTODO|VJOURNAL)$/m) {
244
294
# CLASS=PUBLIC is the default, no need to show it
245
295
s/^CLASS:PUBLIC\r?\n//m;
296
# RELATED=START is the default behavior
297
s/^TRIGGER([^\n:]*);RELATED=START/TRIGGER$1/mg;
298
# VALUE=DURATION is the default behavior
299
s/^TRIGGER([^\n:]*);VALUE=DURATION/TRIGGER$1/mg;
300
s/^(TRIGGER.*):(\S*)/$1 . ":" . NormalizeTrigger($2)/mge;
248
if ($scheduleworld || $egroupware || $synthesis || $addressbook || $funambol ||$google || $mobical || $memotoo) {
303
# Added by EDS >= 2.32, presumably to cache some internal computation.
304
# Because it can be recreated, it doesn't have to be preserved during
305
# sync and such changes can be ignored:
307
# RRULE:BYDAY=SU;COUNT=10;FREQ=WEEKLY | RRULE;X-EVOLUTION-ENDDATE=20080608T
308
# > 070000Z:BYDAY=SU;COUNT=10;FREQ=WEEK
310
s/^(\w+)([^:\n]*);X-EVOLUTION-ENDDATE=[0-9TZ]*/$1$2/mg;
312
if ($scheduleworld || $egroupware || $synthesis || $addressbook || $funambol ||$google || $mobical || $memotoo || $yahoo) {
249
313
# does not preserve X-EVOLUTION-UI-SLOT=
250
314
s/^(\w+)([^:\n]*);X-EVOLUTION-UI-SLOT=\d+/$1$2/mg;
276
340
s/^PHOTO(.*?): .*\n/^PHOTO$1: [...]\n/mg;
277
341
# FN propertiey is not correct
278
342
s/^FN:.*\n/FN$1: [...]\n/mg;
279
# ';' in NOTE is lost by the server: ; in middle is replace by white space
280
# while ; in the end is omitted.
281
while (s!^NOTE:(.*)\\\;(.+)\n!NOTE:$1 $2\n!mg) {}
282
s!^NOTE:(.*)\\\;\n!NOTE:$1\n!mg;
284
while (s!^ORG:(.*)\;(.*)\n!ORG:$1 $2\n!mg) {}
285
343
# Not support car type in telephone
286
344
s!^TEL\;TYPE=CAR(.*)\n!TEL$1\n!mg;
287
345
# some properties are lost
288
s/^(X-EVOLUTION-FILE-AS|NICKNAME|BDAY|CATEGORIES|CALURI|FBURL|ROLE|URL|X-AIM|X-EVOLUTION-UI-SLOT|X-ANNIVERSARY|X-ASSISTANT|X-EVOLUTION-BLOG-URL|X-EVOLUTION-VIDEO-URL|X-GROUPWISE|X-ICQ|X-MANAGER|X-SPOUSE|X-MOZILLA-HTML|X-YAHOO)(;[^:;\n]*)*:.*\r?\n?//gm;
346
s/^(X-EVOLUTION-FILE-AS|NICKNAME|BDAY|CATEGORIES|CALURI|FBURL|ROLE|URL|X-AIM|X-EVOLUTION-UI-SLOT|X-ANNIVERSARY|X-ASSISTANT|X-EVOLUTION-BLOG-URL|X-EVOLUTION-VIDEO-URL|X-GROUPWISE|X-ICQ|X-GADUGADU|X-JABBER|X-MSN|X-SIP|X-SKYPE|X-MANAGER|X-SPOUSE|X-MOZILLA-HTML|X-YAHOO)(;[^:;\n]*)*:.*\r?\n?//gm;
348
#several properties are not preserved by Google in icalendar2.0 format
349
s/^(SEQUENCE|X-EVOLUTION-ALARM-UID)(;[^:;\n]*)*:.*\r?\n?//gm;
351
# Google adds calendar owner as attendee of meetings, regardless
352
# whether it was on the original attendee list. Ignore this
353
# during testing by removing all attendees with @googlemail.com
355
s/^ATTENDEE.*googlemail.com\r?\n//gm;
359
# remove some parameters added by Apple Calendar server in CalDAV
360
s/^(ORGANIZER[^:]*);SCHEDULE-AGENT=NONE/$1/gm;
361
s/^(ORGANIZER[^:]*);SCHEDULE-STATUS=5.3/$1/gm;
362
# seems to require a fixed number of recurrences; hmm, okay...
363
s/^RRULE:COUNT=400;FREQ=DAILY/RRULE:FREQ=DAILY/gm;
365
# X- properties are stored, but not X- parameters
366
s/^(\w+)([^:\n]*);X-EVOLUTION-UI-SLOT=\d+/$1$2/mg;
369
if ($google || $yahoo) {
370
# default status is CONFIRMED
371
s/^STATUS:CONFIRMED\r?\n?//gm;
374
# Google randomly (?!) adds a standard alarm to events.
375
if ($google_valarm) {
376
s/BEGIN:VALARM\nDESCRIPTION:This is an event reminder\nACTION:DISPLAY\nTRIGGER:-PT10M\n(X-KDE-KCALCORE-ENABLED:TRUE\n)END:VALARM\n//s;
380
s/^(X-MICROSOFT-[-A-Z0-9]*)(;[^:;\n]*)*:.*\r?\n?//gm;
381
# some properties cannot be stored
382
s/^(FN)(;[^:;\n]*)*:.*\r?\n?//gm;
291
385
if ($addressbook) {
546
push @items, ${$formatted[0]}[0];
549
return split( /\n/, join( "\n\n", sort @items ));
638
return ${$formatted[0]}[0];
641
# parameters: text, width to use for reformatted lines
642
# returns list of lines without line breaks
651
# split into individual items
652
foreach $_ ( split( /(?:(?<=\nEND:VCARD)|(?<=\nEND:VCALENDAR))\n*/ ) ) {
653
if (/END:VEVENT\s+BEGIN:VEVENT/s) {
654
# remove multiple events from calendar item
655
s/(BEGIN:VEVENT.*END:VEVENT\n)//s;
659
# inject every single one back into the calendar and process the result
660
foreach $event ( split ( /(?:(?<=\nEND:VEVENT))\n*/, $events ) ) {
662
s/\nEND:VCALENDAR/\n$event\nEND:VCALENDAR/;
663
push @items, NormalizeItem($width, $_);
666
# already a single item
667
push @items, NormalizeItem($width, $_);
671
return split( /\n/, join( "\n\n", sort @items ));
552
674
# number of columns available for output:
624
746
open(IN2, "<:utf8", $file2) || die "$file2: $!";
626
@normal1 = Normalize(join("", <IN1>), $singlewidth);
627
@normal2 = Normalize(join("", <IN2>), $singlewidth);
748
my $buf1 = join("", <IN1>);
749
my $buf2 = join("", <IN2>);
750
@normal1 = Normalize($buf1, $singlewidth);
751
@normal2 = Normalize($buf2, $singlewidth);
760
884
if( $#ARGV >= 0 ) {
761
open(IN, "<$ARGV[0]") || die "$ARGV[0]: $!";
885
my $file1 = $ARGV[0];
887
open(IN, "-|:utf8", "find $file1 -type f -print0 | xargs -0 cat") || die "$file1: $!";
889
open(IN, "<:utf8", $file1) || die "$file1: $!";
764
893
$in = *STDIN{IO};
767
print STDOUT join("\n", Normalize(join("", <$in>), $columns)), "\n";
896
my $buf = join("", <$in>);
897
print STDOUT join("\n", Normalize($buf, $columns)), "\n";