Image-ExifTool

 view release on metacpan or  search on metacpan

lib/Image/ExifTool/VCard.pm  view on Meta::CPAN

#------------------------------------------------------------------------------
# File:         VCard.pm
#
# Description:  Read vCard and iCalendar meta information
#
# Revisions:    2015/04/05 - P. Harvey Created
#               2015/05/02 - PH Added iCalendar support
#
# References:   1) http://en.m.wikipedia.org/wiki/VCard
#               2) http://tools.ietf.org/html/rfc6350
#               3) http://tools.ietf.org/html/rfc5545
#------------------------------------------------------------------------------

package Image::ExifTool::VCard;

use strict;
use vars qw($VERSION);
use Image::ExifTool qw(:DataAccess :Utils);

$VERSION = '1.07';

my %unescapeVCard = ( '\\'=>'\\', ','=>',', 'n'=>"\n", 'N'=>"\n" );

# lookup for iCalendar components (used to generate family 1 group names if top level)
my %isComponent = ( Event=>1, Todo=>1, Journal=>1, Freebusy=>1, Timezone=>1, Alarm=>1 );

my %timeInfo = (
    # convert common date/time formats to EXIF style
    ValueConv => q{
        $val =~ s/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z?)/$1:$2:$3 $4:$5:$6$7/g;
        $val =~ s/(\d{4})(\d{2})(\d{2})/$1:$2:$3/g;
        $val =~ s/(\d{4})-(\d{2})-(\d{2})/$1:$2:$3/g;
        return $val;
    },
    PrintConv => '$self->ConvertDateTime($val)',
);

# vCard tags (ref 1/2/PH)
# Note: The case of all tag ID's is normalized to lowercase with uppercase first letter
%Image::ExifTool::VCard::Main = (
    GROUPS => { 2 => 'Document' },
    VARS => { NO_LOOKUP => 1 }, # omit tags from lookup
    NOTES => q{
        This table lists common vCard tags, but ExifTool will also extract any other
        vCard tags found.  Tag names may have "Pref" added to indicate the preferred
        instance of a vCard property, and other "TYPE" parameters may also added to
        the tag name.  VCF files may contain multiple vCard entries which are
        distinguished by the ExifTool family 3 group name (document  number). See
        L<http://tools.ietf.org/html/rfc6350> for the vCard 4.0 specification.
    },
    Version     => { Name => 'VCardVersion',   Description => 'VCard Version' },
    Fn          => { Name => 'FormattedName',  Groups => { 2 => 'Author' } },
    N           => { Name => 'Name',           Groups => { 2 => 'Author' } },
    Bday        => { Name => 'Birthday',       Groups => { 2 => 'Time' }, %timeInfo },
    Tz          => { Name => 'TimeZone',       Groups => { 2 => 'Time' } },
    Adr         => { Name => 'Address',        Groups => { 2 => 'Location' } },
    Geo => {
        Name => 'Geolocation',
        Groups => { 2 => 'Location' },
        # when used as a parameter, VCard 4.0 adds a "geo:" prefix that we need to remove
        ValueConv => '$val =~ s/^geo://; $val',
    },
    Anniversary => { },
    Email       => { },
    Gender      => { },
    Impp        => 'IMPP',
    Lang        => 'Language',
    Logo        => { },
    Nickname    => { },
    Note        => { },
    Org         => 'Organization',
    Photo       => { Groups => { 2 => 'Preview' } },
    Prodid      => 'Software',
    Rev         => 'Revision',
    Sound       => { },
    Tel         => 'Telephone',
    Title       => 'JobTitle',
    Uid         => 'UID',
    Url         => 'URL',
    'X-ablabel' => { Name => 'ABLabel', PrintConv => '$val =~ s/^_\$!<(.*)>!\$_$/$1/; $val' },
    'X-abdate'  => { Name => 'ABDate',  Groups => { 2 => 'Time' }, %timeInfo },
    'X-aim'     => 'AIM',
    'X-icq'     => 'ICQ',
    'X-abuid'   => 'AB_UID',
    'X-abrelatednames' => 'ABRelatedNames',
    'X-socialprofile'  => 'SocialProfile',
);

%Image::ExifTool::VCard::VCalendar = (
    GROUPS => { 1 => 'VCalendar', 2 => 'Document' },
    VARS => {
        NO_LOOKUP => 1, # omit tags from lookup
        LONG_TAGS => 6, # some X-microsoft tags have unavoidably long ID's
    },
    NOTES => q{
        The VCard module is also used to process iCalendar ICS files since they use
        a format similar to vCard.  The following table lists standard iCalendar
        tags, but any existing tags will be extracted.  Top-level iCalendar
        components (eg. Event, Todo, Timezone, etc.) are used for the family 1 group
        names, and embedded components (eg. Alarm) are added as a prefix to the tag
        name.  See L<http://tools.ietf.org/html/rfc5545> for the official iCalendar
        2.0 specification.
    },
    Version     => { Name => 'VCalendarVersion',   Description => 'VCalendar Version' },
    Calscale    => 'CalendarScale',
    Method      => { },
    Prodid      => 'Software',
    Attach      => 'Attachment',
    Categories  => { },
    Class       => 'Classification',
    Comment     => { },
    Description => { },
    Geo => {
        Name => 'Geolocation',
        Groups => { 2 => 'Location' },
        ValueConv => '$val =~ s/^geo://; $val',
    },
    Location    => { Name => 'Location',            Groups => { 2 => 'Location' } },
    'Percent-complete' => 'PercentComplete',
    Priority    => { },
    Resources   => { },
    Status      => { },
    Summary     => { },
    Completed   => { Name => 'DateTimeCompleted',   Groups => { 2 => 'Time' }, %timeInfo },
    Dtend       => { Name => 'DateTimeEnd',         Groups => { 2 => 'Time' }, %timeInfo },
    Due         => { Name => 'DateTimeDue',         Groups => { 2 => 'Time' }, %timeInfo },
    Dtstart     => { Name => 'DateTimeStart',       Groups => { 2 => 'Time' }, %timeInfo },
    Duration    => { },
    Freebusy    => 'FreeBusyTime',
    Transp      => 'TimeTransparency',
    Tzid        => { Name => 'TimezoneID',          Groups => { 2 => 'Time' } },
    Tzname      => { Name => 'TimezoneName',        Groups => { 2 => 'Time' } },
    Tzoffsetfrom=> { Name => 'TimezoneOffsetFrom',  Groups => { 2 => 'Time' } },
    Tzoffsetto  => { Name => 'TimezoneOffsetTo',    Groups => { 2 => 'Time' } },
    Tzurl       => { Name => 'TimeZoneURL',         Groups => { 2 => 'Time' } },
    Attendee    => { },
    Contact     => { },
    Organizer   => { },
    'Recurrence-id' => 'RecurrenceID',
    'Related-to'    => 'RelatedTo',
    Url         => 'URL',
    Uid         => 'UID',
    Exdate      => { Name => 'ExceptionDateTimes',  Groups => { 2 => 'Time' }, %timeInfo },
    Rdate       => { Name => 'RecurrenceDateTimes', Groups => { 2 => 'Time' }, %timeInfo },
    Rrule       => { Name => 'RecurrenceRule',      Groups => { 2 => 'Time' } },
    Action      => { },
    Repeat      => { },
    Trigger     => { },
    Created     => { Name => 'DateCreated',         Groups => { 2 => 'Time' }, %timeInfo },
    Dtstamp     => { Name => 'DateTimeStamp',       Groups => { 2 => 'Time' }, %timeInfo },
    'Last-modified' => { Name => 'ModifyDate',      Groups => { 2 => 'Time' }, %timeInfo },
    Sequence    => 'SequenceNumber',
    'Request-status' => 'RequestStatus',
    Acknowledged=> { Name => 'Acknowledged',        Groups => { 2 => 'Time' }, %timeInfo },
#
# Observed X-tags (not a comprehensive list):
#
    'X-apple-calendar-color'=> 'CalendarColor',
    'X-apple-default-alarm' => 'DefaultAlarm',
    'X-apple-local-default-alarm' => 'LocalDefaultAlarm',
    'X-microsoft-cdo-appt-sequence'     => 'AppointmentSequence',
    'X-microsoft-cdo-ownerapptid'       => 'OwnerAppointmentID',
    'X-microsoft-cdo-busystatus'        => 'BusyStatus',
    'X-microsoft-cdo-intendedstatus'    => 'IntendedBusyStatus',
    'X-microsoft-cdo-alldayevent'       => 'AllDayEvent',
    'X-microsoft-cdo-importance' => {
        Name => 'Importance',
        PrintConv => {
            0 => 'Low',
            1 => 'Normal',
            2 => 'High',
        },
    },
    'X-microsoft-cdo-insttype' => {
        Name => 'InstanceType',
        PrintConv => {
            0 => 'Non-recurring Appointment',
            1 => 'Recurring Appointment',
            2 => 'Single Instance of Recurring Appointment',
            3 => 'Exception to Recurring Appointment',
        },
    },
    'X-microsoft-donotforwardmeeting'   => 'DoNotForwardMeeting',
    'X-microsoft-disallow-counter'      => 'DisallowCounterProposal',
    'X-microsoft-locations' => { Name => 'MeetingLocations', Groups => { 2 => 'Location' } },
    'X-wr-caldesc'          => 'CalendarDescription',
    'X-wr-calname'          => 'CalendarName',
    'X-wr-relcalid'         => 'CalendarID',
    'X-wr-timezone'         => { Name => 'TimeZone2', Groups => { 2 => 'Time' } },
    'X-wr-alarmuid'         => 'AlarmUID',
);

%Image::ExifTool::VCard::VNote = (
    GROUPS => { 1 => 'VNote', 2 => 'Document' },
    NOTES => 'Tags extracted from V-Note VNT files.',
    Version => { },
    Body    => { },
    Dcreated        => { Name => 'CreateDate', Groups => { 2 => 'Time' }, %timeInfo },
    'Last-modified' => { Name => 'ModifyDate', Groups => { 2 => 'Time' }, %timeInfo },
);

#------------------------------------------------------------------------------
# Get vCard tag, creating if necessary
# Inputs: 0) ExifTool ref, 1) tag table ref, 2) tag ID, 3) tag Name,
#         4) source tagInfo ref, 5) lang code
# Returns: tagInfo ref
sub GetVCardTag($$$$;$$)
{
    my ($et, $tagTablePtr, $tag, $name, $srcInfo, $langCode) = @_;
    my $tagInfo = $$tagTablePtr{$tag};
    unless ($tagInfo) {
        if ($srcInfo) {
            $tagInfo = { %$srcInfo };
        } else {
            $tagInfo = { };
            $et->VPrint(0, $$et{INDENT}, "[adding $tag]\n");
        }
        $$tagInfo{Name} = $name;
        delete $$tagInfo{Description};  # create new description
        AddTagToTable($tagTablePtr, $tag, $tagInfo);
    }
    # handle alternate languages (the "language" parameter)
    $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, $langCode) if $langCode;



( run in 0.638 second using v1.01-cache-2.11-cpan-39bf76dae61 )