File Coverage

blib/lib/Lingua/EN/AddressParse/Grammar.pm
Criterion Covered Total %
statement 62 65 95.3
branch 13 16 81.2
condition n/a
subroutine 5 5 100.0
pod n/a
total 80 86 93.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Lingua::EN::AddressParse::Grammar - grammar tree for Lingua::EN::AddressParse
4              
5             =head1 SYNOPSIS
6              
7             Internal functions called from AddressParse.pm module
8              
9             =head1 DESCRIPTION
10              
11             Grammar tree of postal address syntax for Lingua::EN::AddressParse module.
12              
13             The grammar defined here is for use with the Parse::RecDescent module.
14             Note that parsing is done depth first, meaning match the shortest string first.
15             To avoid premature matches, when one rule is a sub set of another longer rule,
16             it must appear after the longer rule. See the Parse::RecDescent documentation
17             for more details.
18              
19             =head1 AUTHOR
20              
21             Lingua::EN::AddressParse::Grammar was written by Kim Ryan, kimryan at cpan d-o-t or g
22              
23             =head1 COPYRIGHT AND LICENSE
24              
25             Copyright (c) 2015 Kim Ryan. All rights reserved.
26              
27             This library is free software; you can redistribute it and/or modify
28             it under the same terms as Perl itself.
29              
30             =cut
31             #-------------------------------------------------------------------------------
32              
33             package Lingua::EN::AddressParse::Grammar;
34 2     2   7 use strict;
  2         3  
  2         40  
35 2     2   6 use warnings;
  2         1  
  2         34  
36 2     2   939 use Locale::SubCountry;
  2         272000  
  2         1601  
37              
38             our $VERSION = '1.25';
39              
40             #-------------------------------------------------------------------------------
41             # Rules that define valid orderings of an addresses components
42             # A (?) refers to an optional component, occurring 0 or more times.
43             # Optional items are returned as an array, which for our case will
44             # always consist of one element, when they exist.
45              
46             my $non_usa_suburban_address_rules =
47             q{
48             full_address :
49              
50             # Note: both sub property and property identifiers should be optional. This
51             # will allow for cases such as 'Lot 123 Xyz Street' where Lot is in effect the house number, even though 'Lot' is grouped as a sub_property label
52             # Also, cases such as 'SHOP 12A, CHAPEL RD STH' have no street number so are incomplete, but still may need to be parsed
53              
54             sub_property(?) property_identifier(?) street_untyped suburb subcountry post_code(?) country(?) non_matching(?)
55             {
56             # block of code to define actions upon successful completion of a
57             # 'production' or rule
58              
59             $return =
60             {
61             # Parse::RecDescent lets you return a single scalar, which we use as
62             # an anonymous hash reference
63             sub_property => $item[1][0],
64             property_identifier => $item[2][0],
65             street_name => $item[3],
66             street_type => '',
67             suburb => $item[4],
68             subcountry => $item[5],
69             post_code => $item[6][0],
70             country => $item[7][0],
71             non_matching => $item[8][0],
72             type => 'suburban'
73             }
74             }
75             |
76              
77             sub_property(?) property_identifier(?) street street_type suburb subcountry post_code(?) country(?) non_matching(?)
78             {
79             $return =
80             {
81             sub_property => $item[1][0],
82             property_identifier => $item[2][0],
83             street_name => $item[3],
84             street_type => $item[4],
85             suburb => $item[5],
86             subcountry => $item[6],
87             post_code => $item[7][0],
88             country => $item[8][0],
89             non_matching => $item[9][0],
90             type => 'suburban'
91             }
92             }
93             |
94              
95              
96             };
97             #-------------------------------------------------------------------------------
98              
99             my $usa_suburban_address_rules =
100             q{
101             full_address :
102              
103              
104             property_identifier(?) street_untyped sub_property(?) suburb subcountry post_code(?) country(?) non_matching(?)
105             # (needs higher precedence than streets with types)
106              
107             {
108             $return =
109             {
110             property_identifier => $item[1][0],
111             street_name => $item[2],
112             street_type => '',
113             sub_property => $item[3][0],
114             suburb => $item[4],
115             subcountry => $item[5],
116             post_code => $item[6][0],
117             country => $item[7][0],
118             non_matching => $item[8][0],
119             type => 'suburban'
120             }
121             }
122             |
123              
124             property_identifier(?) street street_type abbrev_direction(?) sub_property(?) suburb subcountry post_code(?) country(?) non_matching(?)
125             {
126             $return =
127             {
128             property_identifier => $item[1][0],
129             street_name => $item[2],
130             street_type => $item[3],
131             street_direction_suffix => $item[4][0],
132             sub_property => $item[5][0],
133             suburb => $item[6],
134             subcountry => $item[7],
135             post_code => $item[8][0],
136             country => $item[9][0],
137             non_matching => $item[10][0],
138             type => 'suburban'
139             }
140             }
141             |
142              
143             };
144              
145             #-------------------------------------------------------------------------------
146             my $rural_address_rule =
147             q{
148             property_name property_identifier street street_type suburb subcountry post_code(?) country(?) non_matching(?)
149             {
150             $return =
151             {
152             property_name => $item[1],
153             property_identifier => $item[2],
154             street_name => $item[3],
155             street_type => $item[4],
156             suburb => $item[5],
157             subcountry => $item[6],
158             post_code => $item[7][0],
159             country => $item[8][0],
160             non_matching => $item[9][0],
161             type => 'rural'
162             }
163             }
164             |
165             property_name street street_type suburb subcountry post_code(?) country(?) non_matching(?)
166             {
167             $return =
168             {
169             property_name => $item[1],
170             street_name => $item[2],
171             street_type => $item[3],
172             suburb => $item[4],
173             subcountry => $item[5],
174             post_code => $item[6][0],
175             country => $item[7][0],
176             non_matching => $item[8][0],
177             type => 'rural'
178             }
179             }
180             |
181             property_name suburb subcountry post_code(?) country(?) non_matching(?)
182             {
183             $return =
184             {
185             property_name => $item[1],
186             suburb => $item[2],
187             subcountry => $item[3],
188             post_code => $item[4][0],
189             country => $item[5][0],
190             non_matching => $item[6][0],
191             type => 'rural'
192             }
193             }
194             |
195             };
196             #-------------------------------------------------------------------------------
197              
198             my $post_box_rule =
199             q{
200             post_box suburb subcountry post_code(?) country(?) non_matching(?)
201             {
202             $return =
203             {
204             post_box => $item[1],
205             suburb => $item[2],
206             subcountry => $item[3],
207             post_code => $item[4][0],
208             country => $item[5][0],
209             non_matching => $item[6][0],
210             type => 'post_box'
211             }
212             }
213             |
214             };
215             #-------------------------------------------------------------------------------
216              
217             my $road_box_rule =
218             q{
219             road_box street street_type suburb subcountry post_code(?) country(?) non_matching(?)
220             {
221             $return =
222             {
223             road_box => $item[1],
224             street_name => $item[2],
225             street_type => $item[3],
226             suburb => $item[4],
227             subcountry => $item[5],
228             post_code => $item[6][0],
229             country => $item[7][0],
230             non_matching => $item[8][0],
231             type => 'road_box'
232             }
233             }
234             |
235             road_box suburb subcountry post_code(?) country(?) non_matching(?)
236             {
237             $return =
238             {
239             road_box => $item[1],
240             suburb => $item[2],
241             subcountry => $item[3],
242             post_code => $item[4][0],
243             country => $item[5][0],
244             non_matching => $item[6][0],
245             type => 'road_box'
246             }
247             }
248             |
249             };
250              
251             #-------------------------------------------------------------------------------
252              
253             my $non_matching_rule =
254             q{
255             non_matching(?)
256             {
257             $return =
258             {
259             non_matching => $item[1][0],
260             type => 'unknown'
261             }
262             }
263             };
264             #------------------------------------------------------------------------------
265             # Individual components that an address can be composed from. Components are
266             # expressed as literals or Perl regular expressions.
267             #------------------------------------------------------------------------------
268              
269             my $sub_property =
270             q{
271              
272             sub_property:
273              
274             /SUITE \w+ /
275             |
276             sub_property_type unit_number
277             {
278             $return = "$item[1]$item[2]"
279             }
280              
281             # Unit 34, Shop 12C
282              
283             sub_property_type:
284             /(
285             APARTMENT | APT |
286             BAY |
287             DEPARTMENT |
288             FACTORY |
289             FLAT |
290             FRONT |
291             FRNT |
292             GATE |
293             KEY |
294             HANGAR | HNGR |
295             KEY |
296             LOBBY |
297             LBBY |
298             LOT |
299             OFFICE |
300             OFC |
301             LOT |
302             NO |
303             PENTHOUSE |
304             PH |
305             PIER |
306             REAR (OF )? |
307             ROOM |
308             RM |
309             SHOP |
310             SHED |
311             SUITE | STE |
312             TRAILER |
313             TRLR |
314             UNIT |
315             VILLA |
316             \# # Note '#' is a common abbreviation for number in USA
317             )\ /x
318              
319             unit_number:
320             /\d{1,6} / |
321             /\d{1,4}[A-Z]{0,2} / | # such as 23B, 6AW
322             /\d{1,2}[A-Z]\d / | # such as 4A5
323             /[A-Z]{1,2}\d{0,4} / | # such as # D512
324             /\d{1,3}-\d{1,3}/ # such as # 200-204
325             };
326              
327             #------------------------------------------------------------------------------
328              
329             my $property_identifier =
330             q{
331             property_identifier :
332              
333             /\d{1,4} 1\/2 / | # fractional number such as 22 1/2 (half numbers are valid in US)
334             /\d{1,5}-\d{1,5} / | # 1002-1006
335             /\d{1,5}[A-Z]? / # 10025A
336             };
337             #------------------------------------------------------------------------------
338              
339             my $property_name =
340             q{
341             # Property or station names like "Old Regret" or 'Never Fail'
342             property_name : /\"[A-Z'-]{2,}( [A-Z'-]{2,})?\" / |
343             /\'[A-Z-]{2,}( [A-Z-]{2,})?\' /
344             };
345             #------------------------------------------------------------------------------
346              
347             my $post_box =
348             q{
349              
350             post_box : post_box_type post_box_number
351             {
352             $return = "$item[1]$item[2]"
353             }
354              
355             # NOTE: extended regexps not useful here, too many spaces to delimit
356             post_box_type :
357             /GPO BOX / |
358             /LPO BOX / |
359             /P ?O BOX / |
360             /PO BOX / |
361             /LOCKED BAG / |
362             /PRIVATE BAG /
363              
364             post_box_number : /[A-Z]?\d{1,6}[A-Z]? /
365             };
366             #------------------------------------------------------------------------------
367              
368             my $road_box =
369             q{
370              
371             road_box : road_box_type road_box_number
372             {
373             $return = "$item[1]$item[2]"
374             }
375              
376             road_box_type :
377             /(
378             CMB | # Community Mail Bag
379             CMA | # Community Mail Agent
380             CPA | # Community Postal Agent
381             RMS | # Roadside Mail Service
382             RMB | # Roadside Mail Box
383             RSD # Roadside Side Delivery
384             )\ /x # note space separator needed at end of token
385              
386             road_box_number : /[A-Z]?\d{1,5}[A-Z]? /
387              
388             };
389             #------------------------------------------------------------------------------
390              
391             my $street =
392             q{
393              
394             # Streets with no street type such as Road, Lane etc.
395             street_untyped :
396              
397             major_road |
398             avenue_ordinal |
399             street_name_single_word |
400             street_noun |
401             french_style |
402             /AVENUE OF \w+ \w+ / # The Americas, Two Rivers etc
403            
404             major_road :
405             /([N|E|S|W] )?(COUNTY |STATE |US |FIRE )?(ALT|HIGHWAY|LANE|HWY|ROAD|RD|ROUTE) \d{1,3}\w? ([N|E|S|W|NORTH|EAST|SOUTH|WEST] )?/
406              
407             # Avenue C, 12 1/2 etc
408             avenue_ordinal :
409             /([N|E|S|W] )?AVENUE ([A-Z]|\d{1,2}( 1\/2)?) /
410              
411             street_name_single_word:
412             /([N|E|S|W] )?BROADWAY|BOARDWALK|BOULEVARD|BOWERY|ESPLANADE|KINGSWAY|QUEENSWAY|GREENWAY|PARKWAY|RIVERBANK /
413             ...!street_type
414             {
415             $return = $item[1]
416             }
417            
418             street_noun:
419             /(THE|VIA) / any_word
420             {
421             $return = "$item[1]$item[2]"
422             }
423            
424             french_style:
425             /RUE (DE |DES )?/ any_word
426             {
427             $return = "$item[1]$item[2]"
428             }
429              
430             #----------------------------------------------------------------------------
431              
432             # Street name is optional for cases where street name IS in the street_prefix,
433             # like South Parade
434              
435             street: prefix(?) street_name(?)
436             {
437             if ( $item[1][0] and $item[2][0] )
438             {
439             $return = "$item[1][0]$item[2][0]"
440             }
441             elsif ( $item[2][0] )
442             {
443             $return = $item[2][0]
444             }
445             elsif ( $item[1][0] )
446             {
447             $return = $item[1][0]
448             }
449             }
450              
451              
452             prefix :
453             direction |
454             /(
455             NEW|
456             OLD|
457             MT|
458             MOUNT|
459             DAME|
460             SIR|
461             UPPER|
462             LOWER|
463             LA|
464             ST
465             )\ /x
466              
467             street_name :
468              
469             /(N |E |S |W |DR )?(MARTIN LUTHER|MARTIN L|ML) KING ([JS]R )?/
470             |
471             street_name_ordinal
472             |
473              
474             # WORD STREET_TYPE STREET_TYPE
475             # Queen's Park Road, Grand Ridge Rd, Terrace Park Drive, Lane Cove Road etc
476             any_word
477             /(
478             BEND|
479             BRAE|
480             BURN|
481             CAY|
482             CHASE|
483             CIRCLE|
484             CENTRAL|
485             CLUB|
486             CREST|
487             CRESCENT|
488             CROSS|
489             CROSSING|
490             COVE|
491             GARDEN|
492             GLEN|
493             GROVE|
494             HAVEN|
495             HILL|
496             HOLLOW|
497             ISLAND|
498             ISLE|
499             KEY|
500             LANDING|
501             LANE|
502             LOOP|
503             PASS|
504             PARK|
505             PATH|
506             PARKWAY|
507             PLACE|
508             PLAZA|
509             POINT|
510             POINTE|
511             RUN|
512             RIDGE|
513             SQUARE|
514             TRAIL|
515             VIEW|
516             VILLAGE|
517             VISTA
518             )\ /x
519             ...street_type
520             {
521             $return = "$item[1]$item[2]"
522             }
523             |
524              
525             # STREET_TYPE WORD STREET_TYPE
526             # Glen Alpine Way, La Boheme Ave, Grove Valley Ave, Green Bay Road
527             /(
528             CIRCLE|
529             CLUB|
530             CRESCENT|
531             CROSS|
532             GLADE|
533             GLEN|
534             GREENS?|
535             GROVE|
536             FAIRWAY|
537             HOLLOW|
538             HILL|
539             KEY|
540             LA|
541             LANE|
542             LT|
543             PARK|
544             PLAZA|
545             RIDGE|
546             ST
547             )\ /x
548             street_name_word ...street_type
549             {
550             $return = "$item[1]$item[2]"
551             }
552             |
553             # New York State has streets such as 'Dutch Street Road'
554             #any_word /STREET / .../ROAD|RD /
555             #{
556             # $return = "$item[1]$item[2]"
557             #}
558             #|
559              
560             # Allow for street_type that can also occur as a street name, eg Park Lane, Green Street
561             any_word ...street_type
562             {
563             $return = $item[1]
564             }
565             |
566             # such as John F Kennedy Boulevard
567             any_word street_name_letter street_name_word
568             {
569             $return = "$item[1]$item[2]$item[3]"
570             }
571             |
572             street_name_words
573             |
574             street_name_letter
575              
576              
577             # Tin Can Bay (Road), South Head (Road) etc
578             street_name_words : street_name_word(1..3)
579             {
580             if ( $item[1][0] and $item[1][1] and $item[1][2] )
581             {
582             $return = "$item[1][0]$item[1][1]$item[1][2]"
583             }
584             elsif ( $item[1][0] and $item[1][1] )
585             {
586             $return = "$item[1][0]$item[1][1]"
587             }
588             else
589             {
590             $return = $item[1][0]
591             }
592             }
593              
594             # A valid word that forms part of a street name. Use look ahead to prevent the
595             # second name of a two word street_type being consumed too early. For example,
596             # Street in Green Street
597             # Even two letter streets such as 'By Street' are valid
598              
599             street_name_word: ...!street_type /[A-Z'-]{2,}\s+/
600             {
601             $return = $item[2]
602             }
603              
604              
605             # eg Bay 12th Ave, 42nd Street
606             street_name_ordinal :
607             any_word(?)
608             /(
609             \d{0,2}1ST |
610             \d{0,2}2ND |
611             \d{0,2}3RD |
612             \d{0,2}[4-9]TH |
613             \d{0,2}0TH |
614             \d{0,1}11TH |
615             \d{0,1}12TH |
616             \d{0,1}13TH
617             )\ /x
618             {
619              
620             if ( $item[1][0] and $item[2] )
621             {
622             $return = "$item[1][0]$item[2]"
623             }
624             elsif ($item[2] )
625             {
626             $return = "$item[2]"
627             }
628             }
629              
630             street_name_letter: /[A-Z]\s+/ # eg B (Street)
631              
632             street_type:
633              
634             /(
635             # Place most frequent types first to speed up matching
636             ST|STREET|
637             RD|ROAD|
638             LA|LN|LANE|
639             AVE?|AVENUE|
640              
641             ALY?|ALLEY|
642             ARC|ARCADE|
643             BATTLEMENT|
644             BROADWATER|
645             BAYWAY|
646             BVD|BLVD?|BOULEVARDE?|
647             BND|BEND|
648             BL|BOWL|
649             BR|BRAE|
650             BROW|
651             CASCADES|
652             CAY|
653             CENTRE|
654             CONCOURSE|
655             CIR|CIRCLE|CRCLE|
656             CCT|CRT|CIR|CIRCUIT|
657             CHASE|
658             CL|CLOSE|
659             CROSS|CROSSOVER|CROSSING|
660             CR?T|COURT|
661             CV|COVE|
662             CRES|CRS|CR|CRESCENT|
663             CREST|
664             CROFT|
665             DELL|
666             DEVIATION|
667             DRIFTWAY|
668             DR|DRV|DRIVE|
669             ENCLOSURE|
670             ENTRANCE|
671             ESP|ESPLANADE|
672             EXP|EXPW?Y|EXPRESSWAY|
673             FAIRWAY|
674             FW?Y|FREEWAY|
675             GLADE|
676             GRANGE|
677             GLN|GLEN|
678             GREENS?|GRN|
679             GR|GROVE|
680             HAVEN|
681             HL|HILL|
682             HWA?Y|HIGHWAY|
683             HOLLOW|
684             ISLE?|IS|ISLAND|
685             KEY|
686             KNOLL|
687             LANTERNS|
688             LANDING|
689             LOOP|
690             MEWS|
691             OVERFLOW|
692             OVERLOOK|
693             OVAL|
694             PASS|
695             PASSAGE|PSGE|PSG|
696             PATH|
697             PDE|PARADE|
698             PK|PARK|
699             PARKWAY|PKWY|
700             PENINSULA|
701             PIERS|
702             PIKE|
703             PL|PLACE|
704             PLZ|PLAZA|
705             PORTICO|
706             PROMENADE|
707             PT|POINTE?|
708             RAMBLE|
709             RDG|RIDGE|
710             RETREAT|
711             RIDE|RDE|
712             RISE|RSE|
713             RUN|
714             RDY|ROADWAY|
715             ROW|
716             SLIP|
717             SQ|SQUARE|
718             TCE|TER|TRCE|TERRACE|
719             TRL|TRAIL|
720             TPKE|TURNPIKE|
721             TURN|
722             THROUGHWAY|
723             VLG|VILLAGE|
724             WL?K|WALK|
725             WY|WAY|WYNDE|
726             WAYS # such as in 'The Five Ways'
727             )\ /x # note space separator needed at end of token
728             };
729              
730             #------------------------------------------------------------------------------
731             # Suburbs can be up to three words
732             # Examples: Dee Why or St. Johns Park, French's Forest
733              
734             my $suburb =
735             q
736             {
737             suburb_prefix :
738              
739             prefix |
740             /CAPE / |
741             /LAKE / |
742             /MOUNT|MT /
743              
744             suburb:
745             any_word /BY THE SEA /
746             {
747             $return = "$item[1]$item[2]"
748             }
749             |
750             /LAND O LAKES /
751             |
752             # such as Washington Valley, Lane Cove West, Little Egg Harbour Township
753             suburb_prefix(?) any_word suburb_word(0..2)
754             {
755             if ( $item[1][0] )
756             {
757             if ($item[3][0] and $item[3][1])
758             {
759             $return = "$item[1][0]$item[2]$item[3][0]$item[3][1]"
760             }
761             elsif ( $item[3][0] )
762             {
763             $return = "$item[1][0]$item[2]$item[3][0]"
764             }
765             else
766             {
767             $return = "$item[1][0]$item[2]"
768             }
769             }
770             else
771             {
772             if ($item[3][0] and $item[3][1])
773             {
774             $return = "$item[2]$item[3][0]$item[3][1]"
775             }
776             elsif ( $item[3][0] )
777             {
778             $return = "$item[2]$item[3][0]"
779             }
780             else
781             {
782             $return = "$item[2]"
783             }
784             }
785             }
786             |
787             # such as Kippa-ring or Brighton-Le-Sands
788             /[A-Z]{2,}-[A-Z]{2,}(-[A-Z]{2,})? /
789              
790             suburb_word: ...!subcountry any_word
791             };
792             #------------------------------------------------------------------------------
793             my $common_terms =
794             q
795             {
796             # For use in first or second word of double or triple word street names or suburbs
797             # such as Moore Park West
798             any_word: /[A-Z'-]{2,}\s+/
799             {
800             $return = $item[1]
801             }
802              
803             direction: full_direction | abbrev_direction
804              
805             full_direction:
806             /(
807             NORTH |
808             NTH|
809             EAST |
810             SOUTH |
811             STH|
812             WEST
813             )\ /x
814              
815             abbrev_direction:
816             /(
817             N |
818             NE |
819             NW |
820             E |
821             S |
822             SE |
823             SW |
824             W
825             )\ /x
826             };
827              
828             #------------------------------------------------------------------------------
829              
830             # note that Northern territory codes can be abbreviated to 3 digits
831             # Example 0800, 800, 2099
832             my $australian_post_code = q{ post_code: /\d{4} ?/ | /8\d{2} ?/ };
833              
834             my $new_zealand_post_code = q{ post_code: /\d{4} ?/ };
835              
836             # Thanks to Steve Taylor for supplying format of Canadian post codes
837             # Example is K1B 4L7
838             my $canadian_post_code = q{ post_code: /[A-Z]\d[A-Z] \d[A-Z]\d ?/ };
839              
840             # Thanks to Mike Edwards for supplying US zip code formats
841             my $US_post_code = q{ post_code: /\d{5}(-?\d{4})? ?/};
842              
843             # Thanks to Mark Summerfield for supplying UK post code formats
844             # Example is SW1A 9ET
845              
846             my $UK_post_code =
847             q{
848             post_code: outward_code inward_code
849             {
850             $return = "$item[1]$item[2]"
851             }
852              
853             outward_code :
854             /(EC[1-4]|WC[12]|S?W1)[A-Z] / | # London specials
855             /[BGLMS]\d\d? / | # Single letter
856             /[A-Z]{2}\d\d? / # Double letter
857              
858             inward_code : /\d[ABD-HJLNP-UW-Z]{2} ?/
859             };
860              
861              
862             my $Australia =
863             q{
864             country:
865             /(AUSTRALIA|AUST|AU) ?/
866             };
867              
868             my $Canada =
869             q{
870             country:
871             /CANADA ?/
872             };
873              
874             my $New_Zealand =
875             q{
876             country:
877             /(NEW ZEALAND|NZ) ?/
878             };
879              
880             my $US =
881             q{
882             country:
883             /(UNITED STATES OF AMERICA|UNITED STATES|USA?) ?/
884             };
885              
886             my $UK =
887             q{
888             country:
889             /(GREAT BRITAIN|UNITED KINGDOM|UK|GB) ?/
890             };
891              
892             my $non_matching = q{ non_matching: /.*/ };
893              
894             #-------------------------------------------------------------------------------
895             sub _create
896             {
897 4     4   7 my $address = shift;
898              
899             # User can specify country either as full name or 2 letter
900             # abbreviation, such as Australia or AU
901 4         26 my $country = Locale::SubCountry->new($address->{country});
902              
903 4         82 $address->{country_code} = $country->country_code;
904              
905 4         18 my $grammar = '';
906 4 100       13 if ( $address->{country_code} eq 'US' )
907             {
908 1         3 $grammar .= $usa_suburban_address_rules;
909             }
910             else
911             {
912 3         19 $grammar .= $non_usa_suburban_address_rules;
913             }
914              
915 4         14 $grammar .= $rural_address_rule;
916 4         7 $grammar .= $post_box_rule;
917 4         7 $grammar .= $road_box_rule;
918 4         7 $grammar .= $non_matching_rule;
919 4         8 $grammar .= $sub_property;
920 4         7 $grammar .= $property_identifier;
921 4         6 $grammar .= $property_name;
922 4         6 $grammar .= $post_box;
923 4         7 $grammar .= $road_box;
924 4         18 $grammar .= $street;
925 4         9 $grammar .= $suburb;
926 4         6 $grammar .= $common_terms;
927              
928 4         5 my $subcountry_grammar = " subcountry :\n";
929              
930             # Loop over all sub countries to create a grammar for all subcountry
931             # combinations for this country. The grammar for Australia will look like
932             #
933             # subcountry : /NSW / |
934             # /QLD / |
935             # /NEW SOUTH WALES /
936             # /QUEENSLAND / |
937              
938 4         15 my @all_codes = $country->all_codes;
939 4         752 my $last_code = pop(@all_codes);
940              
941 4         9 foreach my $code (@all_codes)
942             {
943 306         309 $subcountry_grammar .= "\t/$code / | \n";
944             }
945             # No alternation character needed for last code
946 4         10 $subcountry_grammar .= "\t/$last_code /\n";
947              
948 4 50       11 if ( not $address->{abbreviated_subcountry_only} )
949             {
950 4         7 $subcountry_grammar .= "| \n";
951              
952 4         13 my @all_full_names = $country->all_full_names;
953 4         704 my $last_full_name = pop(@all_full_names);
954              
955              
956 4         9 foreach my $full_name (@all_full_names)
957             {
958 306         243 $full_name = uc(_clean_sub_country_name($full_name));
959 306         381 $subcountry_grammar .= "\t/$full_name / |\n";
960             }
961              
962 4         7 $last_full_name = _clean_sub_country_name($last_full_name);
963 4         21 $subcountry_grammar .= "\t/$last_full_name /\n";
964             }
965              
966 4         102 $grammar .= $subcountry_grammar;
967              
968 4 100       25 if ( $address->{country_code} eq 'AU' )
    100          
    100          
    50          
    50          
969             {
970 1         2 $grammar .= $australian_post_code;
971 1         2 $grammar .= $Australia;
972              
973             }
974             elsif ( $address->{country_code} eq 'CA' )
975             {
976 1         2 $grammar .= $canadian_post_code;
977 1         2 $grammar .= $Canada;
978             }
979              
980             elsif ( $address->{country_code} eq 'GB' )
981             {
982 1         3 $grammar .= $UK_post_code;
983 1         3 $grammar .= $UK;
984             }
985             elsif ( $address->{country_code} eq 'NZ' )
986             {
987 0         0 $grammar .= $new_zealand_post_code;
988 0         0 $grammar .= $New_Zealand;
989             }
990             elsif ( $address->{country_code} eq 'US' )
991             {
992 1         2 $grammar .= $US_post_code;
993 1         3 $grammar .= $US;
994             }
995             else
996             {
997 0         0 die "Invalid country code or name: $address->{country}";
998             }
999              
1000 4         6 $grammar .= $non_matching;
1001              
1002 4         80 return($grammar);
1003             }
1004             #-------------------------------------------------------------------------------
1005             # Some sub countries contain descriptive text, such as
1006             # "Swansea [Abertawe GB-ATA]" in UK, Wales , which should be removed
1007              
1008             sub _clean_sub_country_name
1009             {
1010 310     310   203 my ($sub_country_name) = @_;
1011              
1012 310         179 my $cleaned_sub_country_name;
1013 310 100       300 if ( $sub_country_name =~ /\[/ )
1014             {
1015             # detect any portion in square brackets
1016 1         7 $sub_country_name =~ /^(\w.*) \[.*\]$/;
1017 1         2 $cleaned_sub_country_name = $1;
1018             }
1019             else
1020             {
1021 309         188 $cleaned_sub_country_name = $sub_country_name;
1022             }
1023 310         329 return($cleaned_sub_country_name)
1024             }
1025             #-------------------------------------------------------------------------------
1026             1;