1# Hey emacs, this is -*-perl-*- ! 2# 3# $Id: Base.pm,v 1.10 2001/01/09 12:04:12 cmdjb Exp $ 4# 5# Metadata::Base - base class for metadata 6# 7# Copyright (C) 1997-2001 Dave Beckett - http://purl.org/net/dajobe/ 8# All rights reserved. 9# 10# This module is free software; you can redistribute it and/or modify 11# it under the same terms as Perl itself. 12# 13 14package Metadata::Base; 15 16require 5.004; 17 18use strict; 19use vars qw($VERSION $Debug); 20 21use Carp; 22 23$VERSION = sprintf("%d.%02d", ('$Revision: 1.10 $ ' =~ /\$Revision:\s+(\d+)\.(\d+)/)); 24 25 26# Class debugging 27$Debug = 0; 28 29sub debug { 30 my $self=shift; 31 # Object debug - have an object reference 32 if (ref ($self)) { 33 my $old=$self->{DEBUG}; 34 $self->{DEBUG}=@_ ? shift : 1; 35 return $old; 36 } 37 38 # Class debug (self is debug level) 39 return $Debug if !defined $self; # Careful, could be debug(0) 40 41 my $old=$Debug; 42 $Debug=$self; 43 $old; 44} 45 46sub whowasi { (caller(1))[3] } 47 48 49# Constructor 50sub new ($%) { 51 my ($type,$self)=@_; 52 $self = {} unless defined $self; 53 54 my $class = ref($type) || $type; 55 bless $self, $class; 56 57 $self->{DEBUG}=$Debug unless defined $self->{DEBUG}; 58 59 $self->{DEFAULT_OPTIONS}={ %$self }; 60 61 # Create empty order if needed 62 $self->{ORDER}=[] if $self->{ORDERED}; 63 64 $self->{ELEMENTS}={}; 65 $self->{ELEMENTS_COUNT}=0; 66 67 warn "@{[&whowasi]}\n" if $self->{DEBUG}; 68 69 $self; 70} 71 72 73# Clone 74sub clone ($) { 75 my $self=shift; 76 77 my $copy=new ref($self); 78 79 my(@order)=$self->{ORDERED} ? @{$self->{ORDER}} : keys %{$self->{ELEMENTS}}; 80 for my $element (@order) { 81 my(@values)=$self->get($element); 82 $copy->set($element, [ @values ]); 83 } 84 85 $copy->{DEBUG}=$self->{DEBUG}; 86 $copy->{DEFAULT_OPTIONS}={ %{$self->{DEFAULT_OPTIONS}} }; 87 88 $copy; 89} 90 91 92sub set ($$$;$) { 93 my $self=shift; 94 return $self->_set('set',@_); 95} 96 97 98sub add ($$$;$) { 99 my $self=shift; 100 return $self->_set('add',@_); 101} 102 103 104sub _set ($$$$;$) { 105 my $self=shift; 106 my $operation=shift; 107 108 my($element,$value,$index)=$self->validate(@_); 109 return if !defined $element; 110 111 if (!defined $self->{ELEMENTS}->{$element}) { 112 # Update order 113 push(@{$self->{ORDER}}, $element) if $self->{ORDERED}; 114 $self->{ELEMENTS_COUNT}++; 115 warn "@{[&whowasi]} Adding new element $element\n" if $self->{DEBUG}; 116 } 117 118 if (ref($value)) { # Assuming eq 'ARRAY' 119 $self->{ELEMENTS}->{$element}=[ @$value ]; 120 warn "@{[&whowasi]} Set $element to values @$value\n" if $self->{DEBUG}; 121 } else { 122 if (defined $index) { 123 # Set new value at a particular index 124 $self->{ELEMENTS}->{$element}->[$index]=$value; 125 } else { 126 if ($operation eq 'add') { 127 # Append value to end of list 128 push(@{$self->{ELEMENTS}->{$element}}, $value); 129 $index=@{$self->{ELEMENTS}->{$element}} - 1; 130 } else { 131 $index='(all)'; 132 $self->{ELEMENTS}->{$element}=[ $value ]; 133 } 134 } 135 warn "@{[&whowasi]} Set $element subvalue $index to value $value\n" if $self->{DEBUG}; 136 } 137} 138 139 140sub get ($$;$) { 141 my $self=shift; 142 my($element,$index)=@_; 143 warn "@{[&whowasi]} Get $element subvalue ", (defined $index) ? $index : "(undefined)","\n" if $self->{DEBUG}; 144 ($element,$index)=$self->validate_elements($element,$index); 145 return if !defined $element; 146 147 warn "@{[&whowasi]} After validate, element $element subvalue ", (defined $index) ? $index : "(undefined)", "\n" if $self->{DEBUG}; 148 149 my $value=$self->{ELEMENTS}->{$element}; 150 return if !defined $value; 151 152 if (defined $index) { 153 return $value->[$index]; 154 } else { 155 return wantarray ? @$value : join(' ', grep (defined $_, @$value)); 156 } 157} 158 159 160sub delete ($$;$) { 161 my $self=shift; 162 my($element,$index)=@_; 163 warn "@{[&whowasi]} element $element subvalue ", (defined $index) ? $index : "(undefined)","\n" if $self->{DEBUG}; 164 ($element,$index)=$self->validate_elements($element,$index); 165 return if !defined $element; 166 167 warn "@{[&whowasi]} After validate, element $element subvalue ", (defined $index) ? $index : "(undefined)", "\n" if $self->{DEBUG}; 168 169 my $value=$self->{ELEMENTS}->{$element}; 170 return if !defined $value; 171 172 my(@old)=@{$value}; 173 if (defined $index) { 174 undef $value->[$index]; 175 # Are all element subvalues missing / undefined? If so, then 176 # allow code below to delete entire element. 177 $index=undef if !grep (defined $_, @{$self->{ELEMENTS}->{$element}}); 178 } 179 180 if (!defined $index) { 181 undef @{$self->{ELEMENTS}->{$element}}; 182 delete $self->{ELEMENTS}->{$element}; 183 $self->{ELEMENTS_COUNT}--; 184 if ($self->{ORDERED}) { 185 @{$self->{ORDER}} = grep ($_ ne $element, @{$self->{ORDER}}); 186 } 187 } 188 return(@old); 189} 190 191 192sub exists ($$;$) { 193 my $self=shift; 194 my($element,$index)=$self->validate_elements(@_); 195 196 return if !exists $self->{ELEMENTS}->{$element}; 197 return 1 if !defined $index; 198 # Trying to find sub-element 199 return $self->{ELEMENTS}->{$element}->[$index]; 200} 201 202 203sub size ($;$) { 204 my $self=shift; 205 my $element=shift; 206 207 return $self->{ELEMENTS_COUNT} if !defined $element; 208 209 return if !exists $self->{ELEMENTS}->{$element}; 210 211 my $value=$self->{ELEMENTS}->{$element}; 212 return scalar(@$value); 213} 214 215 216sub elements ($) { 217 my $self=shift; 218 return @{$self->{ORDER}} if $self->{ORDERED}; 219 return keys %{$self->{ELEMENTS}}; 220} 221 222 223# Old name 224sub fields ($) { 225 sub fields_warn { warn Carp::longmess @_; } 226 fields_warn "Depreciated method called\n"; 227 return shift->elements; 228} 229 230 231sub order ($;@) { 232 my $self=shift; 233 return unless $self->{ORDERED}; 234 235 return @{$self->{ORDER}} if !@_; 236 237 my(@old_order)=@{$self->{ORDER}} if defined wantarray; 238 $self->{ORDER}=[@_]; 239 240 return @old_order if defined wantarray; 241} 242 243 244# Set the given element, value and index? 245sub validate ($$$;$) { 246 my $self=shift; 247 # Not used here 248 #my($self, $element, $value, $index)=@_; 249 return @_; 250} 251 252 253# Check the legality of the given element and index 254sub validate_elements ($$;$) { 255 my $self=shift; 256 # Not used here 257 #my($self, $element, $value, $index)=@_; 258 return @_; 259} 260 261 262# Return a native-formatted version of this metadata 263sub format ($) { 264 my $self=shift; 265 my $string=$self->{ELEMENTS_COUNT}." elements\n"; 266 my(@order)=$self->{ORDERED} ? @{$self->{ORDER}} : keys %{$self->{ELEMENTS}}; 267 $string.="Order: @order\n" if $self->{ORDERED}; 268 for my $element (@order) { 269 for my $value ($self->get($element)) { 270 $string.="$element: $value\n"; 271 } 272 } 273 $string; 274} 275 276 277# Probably possible to do this using symbol table references 278sub as_string ($) { shift->format; } 279 280 281# Pack the metadata as small as possible - binary OK and preferred 282sub pack ($) { 283 my $self=shift; 284 my(@order)=$self->{ORDERED} ? @{$self->{ORDER}} : keys %{$self->{ELEMENTS}}; 285 my $string=''; 286 for my $element (@order) { 287 for my $value ($self->get($element)) { 288 $value='' if !defined $value; 289 $string.="$element\0$value\0"; 290 } 291 } 292 $string; 293} 294 295 296# Read the packed format and restore the same metadata state 297sub unpack ($$) { 298 my $self=shift; 299 my $value=shift; 300 301 return if !defined $value; 302 303 $self->clear; 304 my(@vals)=(split(/\0/,$value)); 305 while(@vals) { 306 my($element,$value)=splice(@vals,0,2); 307 $self->add($element,$value); 308 } 309 310 1; 311} 312 313 314sub read ($) { 315 confess "Not implemented in base class\n"; 316} 317 318 319sub write ($$) { 320 my $self=shift; 321 my $fd=shift; 322 print $fd $self->format; 323} 324 325 326sub reset ($) { 327 my $self=shift; 328 329 my $default_options=$self->{DEFAULT_OPTIONS}; 330 while(my($attr,$value)=each %$default_options) { 331 $self->{$attr}=$value; 332 } 333 334 $self->clear; 335} 336 337 338sub clear ($) { 339 my $self=shift; 340 341 $self->{ELEMENTS}={}; 342 $self->{ELEMENTS_COUNT}=0; 343 344 # Empty order if needed 345 $self->{ORDER}=[] if $self->{ORDERED}; 346} 347 348 349sub get_date_as_seconds ($$) { 350 my $self=shift; 351 iso8601_to_seconds($self->get(shift)); 352} 353 354 355sub set_date_as_seconds ($$$) { 356 my $self=shift; 357 my($element,$value)=shift; 358 $self->set($element, seconds_to_iso8601($value)); 359} 360 361 362sub get_date_as_iso8601 ($$) { 363 my $self=shift; 364 $self->get(shift); 365} 366 367 368sub set_date_as_iso8601 ($$$) { 369 my $self=shift; 370 $self->set(@_); 371} 372 373 374sub seconds_to_iso8601 ($) { 375 my($ss,$mm,$hh,$day,$month,$year)=gmtime(shift); 376 sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ", 377 $year+1900, $month+1,$day,$hh,$mm,$ss); 378} 379 380 381sub iso8601_to_seconds ($) { 382 my $value=shift; 383 my($year,$month,$day,$hh,$mm,$ss,$tz)= ($value =~ m{ 384 ^ 385 (\d\d\d\d) (?: # year YYYY required 386 - (\d\d) (?: # month -MM optional 387 - (\d\d) (?: # day -DD optional 388 T (\d\d) : (\d\d) (?: # time 'T'HH:MM optional 389 (?: : (\d\d (?: \.\d+)?) )? # :SS :SS.frac opt. followed by 390 (Z | (?: [+-]\d+:\d+)) # 'Z' | +/-HH:MM timezone 391 )? # optional TZ/SS/SS+TZ 392 )? # optional THH:MM .. 393 )? # optional -DD... 394 )? # optional -MM... 395 $ 396 }x); 397 398 return if !defined $year; 399 400 # Round to start of year, month, etc. since it is too difficult to round 401 # to the end (leap years). 402 # Really it should return two values for the start & end of period 403 # - maybe in V2.0 404 $month ||=1; $day ||=1; $hh ||=0; $mm ||=0; $ss ||=0; $tz ||='Z'; 405 406 $tz='' if $tz eq 'Z'; 407 408 require 'Time/Local.pm'; 409 410 $value=Time::Local::timegm(int($ss),$mm,$hh,$day,$month-1,$year-1900); 411 412 if ($tz =~ /^(.)(\d+):(\d+)$/) { 413 my $s=(($2*60)+$3)*60; 414 $value= ($1 eq '+') ? $value+$s : $value-$s; 415 } 416 if ($ss=~ /(\.\d+)$/) { 417 $value.= $1; # Note string concatenation 418 } 419 $value; 420} 421 422 423 4241; 425 426__END__ 427 428=head1 NAME 429 430Metadata::Base - base class for metadata 431 432=head1 SYNOPSIS 433 434 package Metadata::FOO 435 436 use vars(@ISA); 437 ... 438 @ISA=qw(Metadata::Base); 439 ... 440 441=head1 DESCRIPTION 442 443Metadata:Base class - the core functionality for handling metadata. 444 445=head1 CONSTRUCTOR 446 447=over 4 448 449=item new [OPTIONS] 450 451Create a new Metadata object with an optional hash of options to describe 452the metadata characteristics. Currently only the following can be set: 453 454=over 4 455 456=item DEBUG 457 458Set if debugging should be enabled from creation. This can also be 459set and read by the B<debug> method below. If this is not defined, 460it is set to the current class debugging state which can be read from 461the class method L<debug> described below. 462 463=item ORDERED 464 465Set if the metadata elements are ordered 466 467=back 468 469=head1 COPY CONSTRUCTOR 470 471=over 4 472 473=item clone 474 475Create a new identical Metadata object from this one. 476 477=back 478 479=head1 CLASS METHODS 480 481=over 4 482 483=item debug [VALUE] 484 485If I<VALUE> is given, sets the debugging state of this class and 486returns the old state. Otherwise returns the current debugging 487state. 488 489=item seconds_to_iso8601 SECONDS 490 491Convert the I<SECONDS> value to (subset of) ISO-8601 format 492YYYY-MM-DDThh:mm:SSZ representing the GMT/UTC value. 493 494=item iso8601_to_seconds VALUE 495 496Convert 6 ISO-8601 subset formats to a seconds value. The 6 formats 497are those proposed for the Dublin Core date use: 498 499 YYYY 500 YYYY-MM 501 YYYY-MM-DD 502 YYYY-MM-DDThh:mm 503 YYYY-MM-DDThh:mm:ssTZ 504 YYYY-MM-DDThh:mm:ss.ssTZ 505 506where TZ can be 'Z', +hh:mm or -hh:mm 507 508B<NOTE>: This method rounds towards the start of the period (it 509should really return two values for start and end). 510 511=back 512 513=head1 METHODS 514 515=over 4 516 517=item debug [VALUE] 518 519If I<VALUE> is given, sets the debugging state of this object and 520returns the old state. Otherwise returns the current debugging 521state. The default debugging state is determined by the class debug 522state. 523 524=item set ELEMENT, VALUE, [INDEX] 525 526Set element I<ELEMENT> to I<VALUE>. If I<VALUE> is an array 527reference, the existing array is used to as all the existing 528sub-values. Otherwise if I<INDEX> is given, sets the particular 529sub-value of I<ELEMENT>, otherwise appends to the end of the existing 530list of sub-values for I<ELEMENT>. 531 532=item get ELEMENT, [INDEX] 533 534Return the contents of the given I<ELEMENT>. In an array context 535returns the sub-values as an array, in a scalar context they are all 536returned separated by spaces. If I<INDEX> is given, returns the value 537of the given sub-value. 538 539=item delete ELEMENT, [INDEX} 540 541Delete the given I<ELEMENT>. If an I<INDEX> is given, remove just 542that sub-value. 543 544=item exists ELEMENT, [INDEX] 545 546Returns a defined value if the given I<ELEMENT> and/or sub-value 547I<INDEX> exists. 548 549=item size [ELEMENT] 550 551Returns number of elements with no argument or the number of subvalues 552for the given I<ELEMENT> or undef if I<ELEMENT> does not exist. 553 554=item elements 555 556Return a list of the elements (in the correct order if there is one). 557 558=item order [ORDER] 559 560If I<ORDER> is given, sets that as the order of the elements and returns 561the old order list. Otherwise, returns the current order of the 562elements. If the elements are not ordered, returns undef. 563 564=item validate ELEMENT, VALUE, [INDEX] 565 566This method is intended to be overriden by subclasses. It is called 567when a element value is being set. The method should return either a 568list of I<ELEMENT>, I<VALUE> and I<INDEX> values to use or an undefined value 569in which case no element will be set. 570 571=item validate_elements ELEMENT, [INDEX] 572 573This method is intended to be overriden by subclasses. It is called 574when a element and/or index is being read. The method should return 575a list of I<ELEMENT> and I<INDEX> values to use. 576 577=item as_string 578=item format 579 580Returns a string representing the metadata, suitable for storing (in 581a text file). This is different from the B<pack> method because this 582value is meant to be the native encoding format of the metadata, 583usually human readable, wheras B<pack> uses the minimum space. 584 585=item pack 586 587Return a packed string representing the metadata format. This can be 588used with B<unpack> to restore the values. 589 590=item unpack VALUE 591 592Initialise the metadata from the packed I<VALUE> which must be the 593value made by the B<pack> method. 594 595=item read HANDLE 596 597Reads from the given file handle and initialises the metadata elements. 598Returns undef if end of file is seen. 599 600=item write HANDLE 601 602Write to the given file handle a formatted version of this metadata 603format. Likely to use B<format> in most implementations. 604 605=item reset 606 607Reset the metadata object to the default ones (including any passed 608with the constructor) and then do a I<clear>. 609 610=item clear 611 612Remove any stored elements in this metadata object. This can be used 613in conjuction with I<read> to prevent the overhead of many I<new> 614operations when reading metadata objects from files. 615 616=item get_date_as_seconds ELEMENT 617 618Assuming I<ELEMENT> is stored in a date format, returns the number of 619seconds since 1st January 1970. 620 621=item set_date_as_seconds ELEMENT, VALUE 622 623Set I<ELEMENT> encoded as a date corresponding to I<VALUE> which is the 624number of seconds since 1st January 1970. 625 626=back 627 628=head1 AUTHOR 629 630By Dave Beckett - http://purl.org/net/dajobe/ 631 632=head1 COPYRIGHT 633 634Copyright (C) 1997-2001 Dave Beckett - http://purl.org/net/dajobe/ 635All rights reserved. 636 637This module is free software; you can redistribute it and/or modify 638it under the same terms as Perl itself. 639 640=cut 641