1package Geo::Postcodes;
2
3#################################################################################
4#                                                                               #
5#           This file is written by Arne Sommer - perl@bbop.org                 #
6#                                                                               #
7#################################################################################
8
9use strict;
10use warnings;
11
12our $VERSION = '0.32';
13
14## Which methods are available ##################################################
15
16my @valid_fields = qw(postcode location borough county type type_verbose owner
17                       address);  # Used by the 'get_fields' procedure.
18
19my %valid_fields;
20
21foreach (@valid_fields)
22{
23  $valid_fields{$_} = 1; # Used by 'is_field' for easy lookup.
24}
25
26## Type Description #############################################################
27
28my %typedesc;
29
30$typedesc{BX}   = "Post Office box";
31$typedesc{ST}   = "Street address";
32$typedesc{SX}   = "Service box";
33$typedesc{IO}   = "Individual owner";
34$typedesc{STBX} = "Street Address and Post Office box";
35$typedesc{MU}   = "Multiple usage";
36$typedesc{PP}   = "Porto Paye receiver";
37
38$typedesc{PN}   = "Place name";
39
40## OO Methods ###################################################################
41
42our %postcode_of;
43our %location_of;
44our %borough_of;
45our %county_of;
46our %type_of;
47our %owner_of;
48our %address_of;
49
50sub new
51{
52  my $class      = shift;
53  my $postcode   = shift;
54  my $self       = shift; # Allow for subclassing.
55
56  return unless valid($postcode);
57
58  unless ($self)
59  {
60    $self = bless \(my $dummy), $class;
61  }
62
63  $postcode_of {$self} =              $postcode;
64  $location_of {$self} = location_of ($postcode);
65  $borough_of  {$self} = borough_of  ($postcode);
66  $county_of   {$self} = county_of   ($postcode);
67  $type_of     {$self} = type_of     ($postcode);
68  $owner_of    {$self} = owner_of    ($postcode);
69  $address_of  {$self} = address_of  ($postcode);
70  return $self;
71}
72
73sub DESTROY
74{
75  my $object_id = $_[0];
76
77  delete $postcode_of {$object_id};
78  delete $location_of {$object_id};
79  delete $borough_of  {$object_id};
80  delete $county_of   {$object_id};
81  delete $type_of     {$object_id};
82  delete $owner_of    {$object_id};
83  delete $address_of  {$object_id};
84}
85
86sub postcode
87{
88  my $self = shift;
89  return unless defined $self;
90  return $postcode_of{$self} if exists $postcode_of{$self};
91  return;
92}
93
94sub location
95{
96  my $self = shift;
97  return unless defined $self;
98  return $location_of{$self} if exists $location_of{$self};
99  return;
100}
101
102sub borough
103{
104  my $self = shift;
105  return unless defined $self;
106  return $borough_of{$self} if exists $borough_of{$self};
107  return;
108}
109
110sub county
111{
112  my $self = shift;
113  return unless defined $self;
114  return $county_of{$self} if exists $county_of{$self};
115  return;
116}
117
118sub type
119{
120  my $self = shift;
121  return unless defined $self;
122  return $type_of{$self} if exists $type_of{$self};
123  return;
124}
125
126sub type_verbose
127{
128  my $self = shift;
129  return unless defined $self;
130  return unless exists $type_of{$self};
131  return unless exists $typedesc{$type_of{$self}};
132  return $typedesc{$type_of{$self}};
133}
134
135sub owner
136{
137  my $self = shift;
138  return unless defined $self;
139  return $owner_of{$self} if exists $owner_of{$self};
140  return;
141}
142
143sub address
144{
145  my $self = shift;
146  return unless defined $self;
147  return $address_of{$self} if exists $address_of{$self};
148  return;
149}
150
151#################################################################################
152
153sub get_postcodes      ## Return all the postcodes, unsorted.
154{
155  return;
156}
157
158sub get_fields         ## Get a list of legal fields for the class/object.
159{
160  return @valid_fields;
161}
162
163sub is_field           ## Is the specified field legal? Can be called as
164{                      ## a procedure, or as a method.
165  my $field = shift;
166  $field    = shift if $field =~ /Geo::Postcodes/; # Called on an object.
167
168  return 1 if $valid_fields{$field};
169  return 0;
170}
171
172## Global Procedures  - Stub Version, Override in your subclass #################
173
174sub legal # Is it a legal code, i.e. something that follows the syntax rule.
175{
176  return 0;
177}
178
179sub valid # Is the code in actual use.
180{
181  return 0;
182}
183
184sub postcode_of
185{
186  return;
187}
188
189sub location_of
190{
191  return;
192}
193
194sub borough_of
195{
196  return;
197}
198
199sub county_of
200{
201  return;
202}
203
204sub type_of
205{
206  return;
207}
208
209sub type_verbose_of
210{
211  return;
212}
213
214sub owner_of
215{
216  return;
217}
218
219sub address_of
220{
221  return;
222}
223
224sub get_types
225{
226  return keys %typedesc;
227}
228
229sub type2verbose
230{
231  my $type = shift;
232  return unless $type;
233  return unless exists $typedesc{$type};
234  return $typedesc{$type};
235}
236
237my %legal_mode;
238   $legal_mode{'and'}  = $legal_mode{'and not'}  = 1;
239   $legal_mode{'nand'} = $legal_mode{'nand not'} = 1;
240   $legal_mode{'nor'}  = $legal_mode{'nor not'}  = 1;
241   $legal_mode{'or'}   = $legal_mode{'or not'}   = 1;
242   $legal_mode{'xnor'} = $legal_mode{'xnor not'} = 1;
243   $legal_mode{'xor'}  = $legal_mode{'xor not'}  = 1;
244
245my %legal_initial_mode;
246   $legal_initial_mode{'all'} = $legal_initial_mode{'none'} = 1;
247   $legal_initial_mode{'not'} = $legal_initial_mode{'one'}  = 1;
248
249sub is_legal_selectionmode
250{
251  my $mode = shift;
252  return 1 if $legal_mode{$mode};
253  return 0;
254}
255
256sub is_legal_initial_selectionmode
257{
258  my $mode = shift;
259  return 1 if $legal_initial_mode{$mode} or $legal_mode{$mode};
260  return 0;
261}
262
263sub get_selectionmodes
264{
265  return sort keys %legal_mode;
266}
267
268sub get_initial_selectionmodes
269{
270  return sort (keys %legal_mode, keys %legal_initial_mode);
271}
272
273sub verify_selectionlist
274{
275  return Geo::Postcodes::_verify_selectionlist('Geo::Postcodes', @_);
276    # Black magic.
277}
278
279sub _verify_selectionlist
280{
281  my $caller_class = shift;
282  my @args         = @_;    # A list of selection arguments to verify
283
284  my $status       = 1;     # Return value
285  my @out          = ();
286  my @verbose      = ();
287
288  return (0, "No arguments") unless @args;
289
290  if (is_legal_initial_selectionmode($args[0]))
291  {
292    my $mode = shift @args;
293
294    if (@args and $args[0] eq "not" and is_legal_initial_selectionmode("$mode $args[0]"))
295    {
296      $mode = "$mode $args[0]";
297      shift @args;
298    }
299
300    push @out, $mode;
301    push @verbose, "Mode: '$mode' - ok";
302
303    return (1, @out) if $mode eq "all" or $mode eq "none";
304    return (1, @out) if $mode eq "one" and @args == 0;
305      # This one can both be used alone, or followed by more.
306
307    return (0, @verbose, "Missing method/value pair - not ok") unless @args >= 2;
308        # Missing method/value pair.
309  }
310
311  ## Done with the first one
312
313  while (@args)
314  {
315    my $argument = shift(@args);
316
317    if ($caller_class->is_field($argument))
318    {
319      push @out, $argument;
320      push @verbose, "Field: '$argument' - ok";
321
322      if (@args)
323      {
324        $argument = shift(@args);
325        push @out, $argument;
326        push @verbose, "String: '$argument' - ok";
327      }
328      else
329      {
330        push @verbose, "Missing string - not ok"; # The last element was a method.
331        $status = 0;
332        @args = (); # Terminate the loop
333      }
334    }
335    elsif (is_legal_selectionmode($argument))
336    {
337      if (@args and $args[0] eq "not" and is_legal_selectionmode("$argument $args[0]"))
338      {
339        $argument = "$argument $args[0]";
340        shift @args;
341      }
342      push @out, $argument;
343      push @verbose, "Mode: '$argument' - ok";
344
345      unless (@args >= 2) # Missing method/value pair
346      {
347        push @verbose, "Missing method/value pair - not ok";
348        $status = 0;
349        @args = (); # Terminate the loop
350      }
351    }
352    elsif ($argument eq 'procedure')
353    {
354      push @out, $argument;
355      push @verbose, "Field: 'procedure' - ok";
356
357      my $procedure = shift(@args);
358      if (ref $procedure eq "CODE")
359      {
360        if (_valid_procedure_pointer($procedure))
361        {
362          push @out, $procedure;
363          push @verbose, "Procedure pointer: '$procedure' - ok";
364        }
365        else
366        {
367          push @verbose, "No such procedure: '$procedure' - not ok";
368          $status = 0;
369          @args   = (); # Terminate the loop
370        }
371      }
372      else
373      {
374        push @verbose, "Not a procedure pointer: '$procedure' - not ok";
375        $status = 0;
376        @args   = (); # Terminate the loop
377      }
378    }
379    else
380    {
381      push @verbose, "Illegal argument: '$argument' - not ok";
382      $status = 0;
383      @args   = (); # Terminate the loop
384    }
385  }
386
387  return (1, @out) if $status; # Return a modified argument list on success.
388
389  return (0, @verbose);        # Return a list of diagnostic meddages on failure.
390}
391
392sub selection_loop
393{
394  return Geo::Postcodes::_selection_loop('Geo::Postcodes', @_);
395    # Black magic.
396}
397
398sub _selection_loop
399{
400  my $caller_class      = shift;
401
402  my $objects_requested = 0; # Not object oriented.
403
404  if ($_[0] eq $caller_class)
405  {
406    $objects_requested  = 1;
407    shift;
408  }
409
410  my $procedure_pointer = shift;
411
412  return 0 unless $procedure_pointer;
413
414  my @selection_clauses = @_;
415  my @postcodes         = _selection($caller_class, @selection_clauses);
416
417  return 0 unless @postcodes;
418
419  foreach (@postcodes)
420  {
421    &$procedure_pointer($objects_requested ? $caller_class->new($_) : $_);
422  }
423  return 1;
424}
425
426
427#################################################################################
428#                                                                               #
429#  Returns a list of postcodes if called as a procedure;                        #
430#    Geo::Postcodes::XX::selection(...)                                         #
431#  Returns a list of objects if called as a method;                             #
432#    Geo::Postcodes::XX->selection(...)                                         #
433#                                                                               #
434# Note that 'or' and 'not' are not written efficient, as they recompile the     #
435# regular expression(s) for every postcode.                                     #
436#                                                                               #
437#################################################################################
438
439sub selection
440{
441  return Geo::Postcodes::_selection('Geo::Postcodes', @_);
442    # Black magic.
443}
444
445sub _selection
446{
447  my $caller_class      = shift;
448
449  my $objects_requested = 0; # Not object oriented.
450
451  if ($_[0] eq $caller_class)
452  {
453    $objects_requested  = 1;
454    shift;
455  }
456
457  if ($_[0] eq 'all')
458  {
459    my @all = sort &{&_proc_pointer($caller_class . '::get_postcodes')}();
460      # Get all the postcodes.
461
462    return @all unless $objects_requested;
463
464    my @out_objects;
465
466    foreach my $postcode (@all)
467    {
468      push(@out_objects, $caller_class->new($postcode));
469    }
470
471    return @out_objects;
472  }
473
474  elsif ($_[0] eq 'none')
475  {
476    return; # Absolutely nothing.
477  }
478
479  my $limit = 0; # Set to one if we have requested only one postcode.
480  if ($_[0] eq "one")
481  {
482    $limit = 1;
483    shift; # Get rid of the mode.
484  }
485
486  my $mode = "and";
487    # The mode defaults to 'and' unless specified.
488
489  my %out = ();
490
491  ## The first set of method/value ##############################################
492
493  my @all = &{&_proc_pointer($caller_class . '::get_postcodes')}();
494    # Get all the postcodes.
495
496  my($field, $current_field, $value, $current_value);
497
498  if (@_) # As 'one' can be without additional arguments.
499  {
500    if (is_legal_initial_selectionmode($_[0]))
501    {
502      if ($_[1] eq "not" and is_legal_initial_selectionmode("$_[0] $_[1]"))
503      {
504        $mode = shift; $mode .= " "; $mode .= shift;
505      }
506      else
507      {
508        $mode = shift if is_legal_initial_selectionmode($_[0]);
509      }
510    }
511
512    $field = shift;
513
514    if ($field eq 'procedure')
515    {
516      my $procedure = shift;
517      return unless _valid_procedure_pointer($procedure);
518
519      my $match;
520
521      foreach my $postcode (@all)
522      {
523        eval { $match = $procedure->($_); };
524        return if $@; # Return if the procedure was uncallable.
525
526        if ($mode =~ /not/) { $out{$postcode}++ unless $match; }
527        else                { $out{$postcode}++ if     $match; }
528      }
529    }
530    else
531    {
532      return unless &{&_proc_pointer($caller_class . '::is_field')}($field);
533        # Return if the specified method is undefined for the class.
534        # As and 'and' with a list with one undefined item gives an empty list.
535
536      my $current_field = &_proc_pointer($caller_class . '::' . $field .'_of');
537
538      $value  = shift; $value =~ s/%/\.\*/g;
539      return unless $value;
540        # A validity check is impossible, so this is the next best thing.
541
542      foreach my $postcode (@all)
543      {
544        $current_value = $current_field->($postcode);
545          # Call the procedure with the current postcode as argument
546
547        next unless $current_value;
548          # Skip postcodes without this field.
549
550        my $match = $current_value =~ m{^$value$}i; ## Case insensitive
551
552        if ($mode =~ /not/) { $out{$postcode}++ unless $match; }
553        else                { $out{$postcode}++ if     $match; }
554      }
555    }
556
557    $mode = 'and' if $mode eq 'not';
558  }
559
560  elsif ($limit) # just one argument; 'one'.
561  {
562    map { $out{$_} = 1 } @all
563  }
564
565  while (@_)
566  {
567    if (is_legal_selectionmode($_[0]))
568    {
569      if ($_[1] eq "not" and is_legal_selectionmode("$_[0] $_[1]"))
570      {
571        $mode = shift; $mode .= " "; $mode .= shift;
572      }
573      else
574      {
575        $mode = shift if is_legal_selectionmode($_[0]);
576      }
577    }
578
579    # Use the one already on hand, if none is given.
580
581    my $is_procedure = 0;
582    my $procedure;
583
584    $field = shift;
585
586    if ($field eq 'procedure')
587    {
588      $is_procedure = 1;
589      $procedure = shift;
590      return unless _valid_procedure_pointer($procedure);
591    }
592    else
593    {
594      return unless &{&_proc_pointer($caller_class . '::is_field')}($field);
595        # Return if the specified method is undefined for the class.
596        # As an 'and' with a list with one undefined item gives an empty list.
597
598      $current_field = &_proc_pointer($caller_class . '::' . $field .'_of');
599
600      $value = shift;
601      $value =~ s/%/\.\*/g;
602      return unless $value;
603        # A validity check is impossible, so this is the next best thing.
604    }
605
606    foreach my $postcode ($mode =~ /and/ ? (keys %out) : @all)
607    {
608      # We start with the result from the previous iteration if the mode
609      # is one of the 'and'-family. Otherwise it is one of the 'or'-family,
610      # and we have to start from scratch (@all).
611
612      my $match;
613
614      if ($procedure)
615      {
616        eval { $match = $procedure->($postcode); };
617        return if $@; # Return if the procedure was uncallable.
618      }
619      else
620      {
621        $current_value = $current_field->($postcode);
622          # Call the procedure with the current postcode as argument
623
624        next unless $current_value;
625          # Skip postcodes without this field.
626
627        $match = $current_value =~ m{^$value$}i; ## Case insensitive
628      }
629
630      if    ($mode eq "and")
631      {
632        delete $out{$postcode} unless $match;
633      }
634      elsif ($mode eq "and not")
635      {
636        delete $out{$postcode} if     $match;
637      }
638
639      elsif ($mode eq "nand")
640      {
641        if ($match and $out{$postcode})   { delete $out{$postcode} if $out{$postcode}; }
642        else                              { $out{$postcode}++;                         }
643      }
644      elsif ($mode eq "nand not")
645      {
646        if (!$match and $out{$postcode})  { delete $out{$postcode} if $out{$postcode}; }
647        else                              { $out{$postcode}++;                         }
648      }
649
650      elsif ($mode eq "or")
651      {
652        $out{$postcode}++      if     $match;
653      }
654      elsif ($mode eq "or not")
655      {
656        $out{$postcode}++      unless $match;
657      }
658      elsif ($mode eq "nor")
659      {
660        if (!$match and !$out{$postcode}) { $out{$postcode}++;                         }
661        else                              { delete $out{$postcode} if $out{$postcode}; }
662      }
663      elsif ($mode eq "nor not")
664      {
665        if ($match and !$out{$postcode})  { $out{$postcode}++;                         }
666        else                              { delete $out{$postcode} if $out{$postcode}; }
667      }
668      elsif ($mode eq "xor")
669      {
670        if ($match)
671        {
672          if ($out{$postcode}) { delete $out{$postcode}; }
673          else                 { $out{$postcode}++;      }
674        }
675      }
676      elsif ($mode eq "xor not")
677      {
678        unless ($match)
679        {
680           if ($out{$postcode}) { delete $out{$postcode}; }
681           else                 { $out{$postcode}++;      }
682        }
683      }
684
685      elsif ($mode eq "xnor")
686      {
687        my $boolean = $out{$postcode} ? 1 : 0;
688        if ($match == $boolean)
689        {
690          $out{$postcode}++;
691        }
692        else
693        {
694          delete $out{$postcode} if $out{$postcode};
695        }
696      }
697      elsif ($mode eq "xnor not")
698      {
699        my $boolean = $out{$postcode} ? 1 : 0;
700        if ($match != $boolean)
701        {
702          $out{$postcode}++;
703        }
704        else
705        {
706          delete $out{$postcode} if $out{$postcode};
707        }
708      }
709    }
710  }
711
712  ###############################################################################
713
714  return unless %out;
715    # Return nothing if we have an empty list (or rather, hash).
716
717  my @out;
718
719  if ($limit)                   # The caller has requested just one postcode,   #
720  {                             #  and will get exactly that if any matches     #
721    my @list = keys %out;       #  were found. The returned postcode is chosen  #
722    @out = $list[rand(@list)];  #  by random.                                   #
723  }
724  else
725  {
726    @out = sort keys %out;
727      # This will give an ordered list, as opposed to a semi random order. This #
728      # is essential when comparing lists of postcodes, as the test scripts do. #
729  }
730
731  ###############################################################################
732
733  return @out unless $objects_requested;
734
735  my @out_objects;
736
737  foreach my $postcode (@out)
738  {
739    push(@out_objects, $caller_class->new($postcode));
740  }
741
742  return @out_objects;
743}
744
745
746sub _proc_pointer
747{
748  my $procedure_name = shift;
749  return \&{$procedure_name};
750}
751
752sub _valid_procedure_pointer
753{
754  my $ptr = shift;
755  return 0 if ref $ptr ne "CODE";
756  return 1 if defined(&$ptr);
757  return 0;
758}
759
7601;
761__END__
762
763=head1 NAME
764
765Geo::Postcodes - Base class for the Geo::Postcodes::* modules
766
767=head1 SYNOPSIS
768
769This module should not be used directly from application programs, but from a
770country subclass; e.g.:
771
772 package Geo::Postcodes::U2;
773
774 use Geo::Postcodes 0.30;
775 use base qw(Geo::Postcodes);
776
777 use strict;
778 use warnings;
779
780 our $VERSION = '0.30';
781
782And so on. See the documentation for making country subclasses for the gory
783details; I<perldoc Geo::Postcodes::Subclass> or I<man Geo::Postcodes::Subclass>.
784
785=head1 ABSTRACT
786
787Geo::Postcodes - Base class for the Geo::Postcodes::* modules. It is
788useless on its own.
789
790=head1 PROCEDURES AND METHODS
791
792These procedures and methods should, with a few exceptions, not be used directly,
793but from a country module. See the documentation for the indiviual country modules
794for usage details.
795
796=head2 address, borough, county, location, owner, postcode, type, type_verbose
797
798Methods for accessing the fields of a postcode object. The individual country
799modules can support as many of them as needed, and add new ones.
800
801=head2 address_of, borough_of, county_of, location_of, owner_of, postcode_of,
802       type_of, type_verbose_of
803
804Procedures that returns the value of the corresponding field for the given postcode.
805They will return I<undef> if the postcode does not exist, or the field is without
806value for the given postcode.
807
808=head2 get_fields, is_field
809
810I<get_fields()> will return a list of all the fields supported by the module, and
811I<is_field($field)> will return true (1) if the specified field is supported by
812the module.
813
814=head2 legal, valid
815
816Procedures that return I<true> if the postcode is legal (syntactically), or valid
817(in actual use).
818
819=head2 new
820
821This will create a new postcode object.
822
823=head2 selection, selection_loop
824
825Procedures/methods for selecting several postcodes at once.
826
827See the selection manual (I<perldoc Geo::Postcodes::Selection> or
828I<man Geo::Postcodes::Selection>) for usage details, and the tutorial
829(I<perldoc Geo::Postcodes::Tutorial> or I<man Geo::Postcodes::Tutorial>)
830for sample code.
831
832=head2 verify_selectionlist, is_legal_selectionmode, is_legal_initial_selectionmode
833       get_selectionmodes, get_initial_selectionmodes
834
835Supporting procedures when using I<selection> or I<selection_loop>.
836
837See the selection manual; I<perldoc Geo::Postcodes::Selection> or
838I<man Geo::Postcodes::Selection> for usage details.
839
840=head2 get_postcodes
841
842This will return an unsorted list of all the postcodes.
843
844=head2 get_types
845
846This will return a list of types.  See the next section.
847
848=head2 type2verbose
849
850  my $type_as_english_text  = $Geo::Postcodes::type2verbose($type);
851  my $type_as_national_text = $Geo::Postcodes::U2:type2verbose($type);
852
853This procedure gives an english description of the type. Use the child class
854directly for a description in the native language.
855
856=head1 TYPE
857
858This class defines the following types for the postal locations:
859
860=over
861
862=item BX
863
864Post Office box
865
866=item ST
867
868Street address
869
870=item SX
871
872Service box (as a Post Office box, but the mail is delivered to
873the customer).
874
875=item IO
876
877Individual owner (a company with its own postcode).
878
879=item STBX
880
881Either a Street address (ST) or a Post Office box (BX)
882
883=item MU
884
885Multiple usage (a mix of the other types)
886
887=item PP
888
889Porto Paye receiver (mail where the reicever will pay the postage).
890
891=item PN
892
893Place name
894
895=back
896
897The child classes can use them all, or only a subset, but must not define
898their own additions. The child classes are responsible for adding descriptions
899in the native language, if appropriate.
900
901=head1 DESCRIPTION
902
903This is the base class for the Geo::Postcodes::* modules.
904
905=head1 CAVEAT
906
907This module uses I<inside out objects>, see for instance
908L<http://www.stonehenge.com/merlyn/UnixReview/col63.html> for a discussion of
909the concept.
910
911=head1 SEE ALSO
912
913See also the selection manual (I<perldoc Geo::Postcodes::Selection> or
914I<man Geo::Postcodes::Selection>) for usage details, the tutorial
915(I<perldoc Geo::Postcodes::Tutorial> or I<man Geo::Postcodes::Tutorial>)
916for sample code, and the ajax tutorial (I<perldoc Geo::Postcodes::Ajax> or
917I<man Geo::Postcodes::Ajax>) for information on using the modules in
918combination with ajax code in a html form to get the location updated
919automatically.
920
921The latest version of this library should always be available on CPAN, but see
922also the library home page; F<http://bbop.org/perl/GeoPostcodes> for additional
923information and sample usage. The child classes that can be found there have
924some sample programs.
925
926=head1 COPYRIGHT AND LICENCE
927
928Copyright (C) 2006 by Arne Sommer - perl@bbop.org
929
930This library is free software; you can redistribute them and/or modify
931it under the same terms as Perl itself.
932
933=cut
934