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