Saturday June 11, 2005
07:20 AM
Foundation vs. Mac::Propertylist
I needed to access iPhoto's AlbumData.xml file in order to read my iPhoto database.
Mac::iPhoto did not work for me it always failed loading the xml-file (now I know why).
Mac::PropertyList would work after massaging the XML data before handing it of to plist_parse. However the solution was painfully slow. My 600KB AlbumData.xml file took more than 30 secs to load and parse on a Mac Mini.
So I looked into other ways to load the data from AlbumData.xml. Via PerlObjCBridge I implemented some code using NSPropertyListSerialization. I'm happy to report that the 30 secs have turned into 3 secs. That's better ...
The data returned from plistToHash and loadiPhotoDB are different! There are some data types missing in the plistTraverse() sub!
--------------------------------------------------
use strict;
use Foundation;
use Mac::PropertyList;
use Time::HiRes qw{gettimeofday tv_interval};
use constant XML => qq{$ENV{HOME}/Pictures/iPhoto Library/AlbumData.xml};
my $t0 = [gettimeofday];
my $hash=plistToHash(XML);
my $elapsed = tv_interval($t0);
print "using plistToHash = $elapsed\n";
$t0 = [gettimeofday];
$hash=loadiPhotoDB(XML);
$elapsed = tv_interval($t0);
print "using Mac::PropertyList = $elapsed\n";
sub plistToHash {
my($filename)=@_;
my $data=NSData->dataWithContentsOfFile_($filename);
return undef unless($data);
my $plist=NSPropertyListSerialization->propertyListFromData_mutabilityOption_format _errorDescription_($data,0,undef,undef);
return undef unless($plist);
my %dict=();
return plistTraverse(\%dict,$plist,'dict',0);
}
sub plistTraverse {
my($dest,$src,$type,$depth)=@_;
my $e=($type eq 'dict')?$src->keyEnumerator():$src->objectEnumerator;
while(my $next = $e->nextObject()) {
last unless($$next);
my $obj=($type eq 'dict')?$src->objectForKey_($next):$next;
my $class=$obj->className->cString();
my $keyString=($type eq 'dict')?$next->cString:"";
if($class =~ /dictionary$/i){
my %dict=();
my $sub=plistTraverse(\%dict,$obj,'dict',$depth+1);
if($type eq 'dict') {
$dest->{$keyString}=$sub;
}else{
push(@$dest,$sub);
}
}elsif($class =~ /array$/i){
my @array=();
my $sub=plistTraverse(\@array,$obj,'array',$depth+1);
if($type eq 'dict') {
$dest->{$keyString}=$sub;
}else{
push(@$dest,$sub);
}
}elsif($class =~ /string$/i){
if($type eq 'dict') {
$dest->{$keyString}=$obj->cString;
} else {
push(@$dest,$obj->cString);
}
}elsif($class =~ /number$/i){
if($type eq 'dict') {
$dest->{$keyString}=$obj->doubleValue;
} else {
push(@$dest,$obj->doubleValue);
}
}elsif($class =~ /boolean$/i){
if($type eq 'dict') {
$dest->{$keyString}=($obj->boolValue eq 'YES')?1:0;
} else {
push(@$dest,($obj->boolValue eq 'YES')?1:0);
}
} else {
print STDERR "**** unhandled class: $class\n";
}
}
return $dest;
}
sub loadiPhotoDB {
my($catalogPath)=@_;
my $xml;
open(CATALOG, $catalogPath) || return undef;
{local $/=undef;$xml=<CATALOG>;}
close(CATALOG);
# Mac::PropertyList is pretty strict about what it expects to
# see in the XML file. We are trimming the file before handing
# it off to parse_plist
$xml =~ s{^.*<plist\s*.*?>\s*}{}s;
$xml =~ s{\s*</plist\s*.*?>\s*\z}{}s;
my $dict=Mac::PropertyList::parse_plist($xml);
return $dict;
}