#------------------------------------------------------------------------------
# File:         PLIST.pm
#
# Description:  Read Apple PLIST-format XML information
#
# Revisions:    2013-02-01 - P. Harvey Created
#
# References:   1) http://www.apple.com/DTDs/PropertyList-1.0.dtd
#
# Notes:      - Sony MODD files also use this format, but with a few quirks
#
#             - Decodes the newer OS X XML-based PLIST format, not the older
#               format used under Classic Mac OS
#------------------------------------------------------------------------------

package Image::ExifTool::PLIST;

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

$VERSION = '1.01';

# PLIST tags (defined on-the-fly for most tags)
%Image::ExifTool::PLIST::Main = (
    PROCESS_PROC => \&ProcessPLIST,
    GROUPS => { 0 => 'XML', 1 => 'PLIST', 2 => 'Document' },
    VARS => { LONG_TAGS => 4 },
    NOTES => q{
        Apple XML Property List tags.  ExifTool will extract PLIST information even
        it isn't listed in the table below.
    },
#
# tags found in PLIST information of QuickTime iTunesInfo iTunMOVI atom (ref PH)
#
    'cast//name'          => { Name => 'Cast',          List => 1 },
    'directors//name'     => { Name => 'Directors',     List => 1 },
    'producers//name'     => { Name => 'Producers',     List => 1 },
    'screenwriters//name' => { Name => 'Screenwriters', List => 1 },
    'codirectors//name'   => { Name => 'Codirectors',   List => 1 }, # (NC)
    'studio//name'        => { Name => 'Studio',        List => 1 }, # (NC)
#
# tags found in MODD files (ref PH)
#
    'MetaDataList//DateTimeOriginal' => {
        Name => 'DateTimeOriginal',
        Description => 'Date/Time Original',
        Groups => { 2 => 'Time' },
        # Sony uses a "real" here -- number of days since Dec 31, 1899
        ValueConv => 'IsFloat($val) ? ConvertUnixTime(($val - 25569) * 24 * 3600 + 0.5) : $val',
        PrintConv => '$self->ConvertDateTime($val)',
    },
    'MetaDataList//Duration' => {
        Name => 'Duration',
        Groups => { 2 => 'Video' },
        PrintConv => 'ConvertDuration($val)',
    },
    'MetaDataList//Geolocation/Latitude' => {
        Name => 'GPSLatitude',
        Groups => { 2 => 'Location' },
        PrintConv => q{
            require Image::ExifTool::GPS;
            Image::ExifTool::GPS::ToDMS($self, $val, 1, 'N');
        },
    },
    'MetaDataList//Geolocation/Longitude' => {
        Name => 'GPSLongitude',
        Groups => { 2 => 'Location' },
        PrintConv => q{
            require Image::ExifTool::GPS;
            Image::ExifTool::GPS::ToDMS($self, $val, 1, 'E');
        },
    },
    'MetaDataList//Geolocation/MapDatum' => {
        Name => 'GPSMapDatum',
        Groups => { 2 => 'Location' },
    },
    XMLFileType => {
        # recognize MODD files by their content
        RawConv => q{
            if ($val eq 'ModdXML' and $$self{FILE_TYPE} eq 'XMP') {
                $self->OverrideFileType('MODD');
            }
            return $val;
        },
    },
);

#------------------------------------------------------------------------------
# We found a PLIST XML property name/value
# Inputs: 0) ExifTool object ref, 1) tag table ref
#         2) reference to array of XML property names (last is current property)
#         3) property value, 4) attribute hash ref (not used here)
# Returns: 1 if valid tag was found
sub FoundTag($$$$;$)
{
    my ($exifTool, $tagTablePtr, $props, $val, $attrs) = @_;
    return 0 unless @$props;
    my $verbose = $exifTool->Options('Verbose');
    my $keys = $$exifTool{PListKeys} || ( $$exifTool{PListKeys} = [] );

    my $prop = $$props[-1];
    if ($verbose > 1) {
        $exifTool->VPrint(0, $$exifTool{INDENT}, '[', join('/',@$props), ' = ',
            $exifTool->Printable($val), "]\n");
    }
    # un-escape XML character entities
    $val = Image::ExifTool::XMP::UnescapeXML($val);

    # handle the various PLIST properties
    if ($prop eq 'data') {
        if ($val =~ /^[0-9a-f]+$/ and not length($val) & 0x01) {
            # MODD files use ASCII-hex encoded "data"...
            my $buff = pack('H*', $val);
            $val = \$buff;
        } else {
            # ...but the PLIST DTD specifies Base64 encoding
            $val = Image::ExifTool::XMP::DecodeBase64($val);
        }
    } elsif ($prop eq 'date') {
        $val = Image::ExifTool::XMP::ConvertXMPDate($val);
    } elsif ($prop eq 'true' or $prop eq 'false') {
        $val = ucfirst $prop;
    } else {
        # convert from UTF8 to ExifTool Charset
        $val = $exifTool->Decode($val, 'UTF8');
        if ($prop eq 'key') {
            if (@$props <= 3) { # top-level key should be plist/dict/key
                @$keys = ( $val );
            } else {
                # save key names to be used in tag name
                push @$keys, '' while @$keys < @$props - 3;
                pop @$keys while @$keys > @$props - 2;
                $$keys[@$props - 3] = $val;
            }
            return 0;
        }
    }

    return 0 unless @$keys; # can't store value if no associated key

    my $tag = join '/', @$keys;     # generate tag ID from 'key' values
    my $tagInfo = $$tagTablePtr{$tag};
    unless ($tagInfo) {
        $exifTool->VPrint(0, $exifTool->{INDENT}, "[adding $tag]\n") if $verbose;
        # generate tag name from ID
        my $name = join('', map(ucfirst, @$keys));
        $name =~ s/([^A-Za-z])([a-z])/$1\u$2/g; # capitalize words
        $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters
        $name =~ s/^MetaDataList//;  # shorten long MODD metadata tag names
        # remove unnecessary "Name" property
        $name =~ s/Name$// if @$keys > 1 and $$keys[-1] eq 'name';
        $tagInfo = { Name => $name, List => 1 };
        if ($prop eq 'date') {
            $$tagInfo{Groups}{2} = 'Time';
            $$tagInfo{PrintConv} = '$self->ConvertDateTime($val)';
        }
        AddTagToTable($tagTablePtr, $tag, $tagInfo);
    }
    # allow list-behaviour only for consecutive tags with the same ID
    if ($$exifTool{LastPListTag} and $$exifTool{LastPListTag} ne $tagInfo) {
        delete $$exifTool{LIST_TAGS}{$$exifTool{LastPListTag}};
    }
    $$exifTool{LastPListTag} = $tagInfo;
    # save the tag
    $exifTool->HandleTag($tagTablePtr, $tag, $val);

    return 1;
}

#------------------------------------------------------------------------------
# Extract information from a PLIST file
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
# Returns: 1 on success, 0 if this wasn't a valid PLIST file
sub ProcessPLIST($$)
{
    my ($exifTool, $dirInfo) = @_;
    my $tagTablePtr = GetTagTable('Image::ExifTool::PLIST::Main');

    $$dirInfo{XMPParseOpts}{FoundProc} = \&FoundTag;

    my $result = Image::ExifTool::XMP::ProcessXMP($exifTool, $dirInfo, $tagTablePtr);

    unless ($result) {
        # check for old Classic MacOS PLIST format (currently not supported)
        my $buff;
        my $raf = $$dirInfo{RAF} or return 0;
        $raf->Seek(0,0) and $raf->Read($buff, 256) or return 0;
        # (encoded as UCS-2BE with leading BOM)
        if ($buff =~ /^\xfe\xff\x00/) {
            $exifTool->Error('Old PLIST format currently not supported');
            $result = 1;
        }
    }
    return $result;
}

1;  # end

__END__

=head1 NAME

Image::ExifTool::PLIST - Read Apple PLIST-format XML information

=head1 SYNOPSIS

This module is used by Image::ExifTool

=head1 DESCRIPTION

This module contains definitions required by Image::ExifTool to decode
information from Apple Property List XML files.

=head1 NOTES

This module decodes the newer OS X XML-based PLIST format, not the older
Classic Mac OS format.

=head1 AUTHOR

Copyright 2003-2013, Phil Harvey (phil at owl.phy.queensu.ca)

This library is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=head1 REFERENCES

=over 4

=item L<http://www.apple.com/DTDs/PropertyList-1.0.dtd>

=back

=head1 SEE ALSO

L<Image::ExifTool::TagNames/PLIST Tags>,
L<Image::ExifTool(3pm)|Image::ExifTool>

=cut

