1#!/usr/local/bin/perl
2# ---------------------------------------------------------------- #
3#   AjaxZip 2.0 - Ajax郵便番号→住所自動入力フォーム(CGI不要版)用
4#   郵便番号一覧 CSV ファイルを JSON 形式に変換するスクリプト
5#   http://www.kawa.net/works/ajax/ajaxzip2/ajaxzip2.html
6#   (c) 2001-2007 Kawasaki Yusuke. All rights reserved.
7# ---------------------------------------------------------------- #
8    use strict;
9    use utf8;
10    use Encode;                     # 文字コード変換モジュール弾
11    use lib qw( lib );
12    use JSON;                       # JSON.pm がデフォルトです
13#   use JSON::Syck;                 # JSON::Syckがあれば利用可能です
14# ---------------------------------------------------------------- #
15    my $VERSION   = '2.11';
16    my $CSV_ENC   = 'CP932';        # CSVファイルのエンコーディング
17    my $JSON_ENC  = 'utf8';         # JSONファイルのエンコーディング
18    my $DISP_ENC  = 'utf8';         # 表示用のエンコーディング
19    my $CSV_FILE  = 'ken_all.csv';  # 入力元CSVファイル名(デフォルト)
20    my $CSV_JIGYO = 'jigyosyo.csv'; # 事業所要郵便番号CSVファイル(オプション)
21    my $JSON_BASE = '../data/zip-%s.json';  # 出力先JSONファイル名
22# ---------------------------------------------------------------- #
23    # local $| = 1;
24    &main( @ARGV );
25# ---------------------------------------------------------------- #
26sub main {
27    my $csvfile  = shift || $CSV_FILE;
28    my $csvjigyo = shift || $CSV_JIGYO;
29
30    my $out = {};
31    &read_ken_all( $out, $csvfile );
32    &read_jigyosyo( $out, $csvjigyo )
33    &write_json( $out );
34}
35# ---------------------------------------------------------------- #
36# 読み仮名データの促音・拗音を小書きで表記したもの
37# http://www.post.japanpost.jp/zipcode/dl/kogaki.html
38# http://www.post.japanpost.jp/zipcode/dl/kogaki/lzh/ken_all.lzh
39# ---------------------------------------------------------------- #
40sub read_ken_all {
41    my $out     = shift or return;
42    my $csvfile = shift;
43    my $prev    = "";
44    my $c       = 0;
45
46    print STDERR "ken_all:\t$csvfile\n";
47    open( CSV, $csvfile ) or die "$! - $csvfile\n";
48    while ( my $iline = <CSV> ) {
49        last if ( $iline =~ /^\x1a/ );  # EOF
50
51        # UTF-8コードで処理する
52        $iline = Encode::decode( $CSV_ENC, $iline );
53
54        # CSVとはいっても「,」の文字は住所には利用されていないので簡易処理
55        my @r = split( ",", $iline );
56        s/^"(.*)"$/$1/s foreach ( @r );
57
58        # 第1・3・10・15カラムは、確実に数字のみのハズ
59        if ( $r[0]  !~ m#^\d{5}$# ||
60             $r[2]  !~ m#^\d{7}$# ||
61             $r[9]  !~ m#^\d{1}$# ||
62             $r[14] !~ m#^\d{1}[\r\n]+$# ) {
63            die "Invalid Data Source: $csvfile (ken_all.csv)\n$iline\n";
64        }
65
66        # 全角かっこ『(』が入る場合は、フリガナからも半角かっこ『(』を除外
67        if ( $r[8] =~ s/((.)+$//s ) {
68            $r[5] =~ s/\([^\(]+$//s;
69        }
70
71        # 岩手県    和賀郡西和賀町  杉名畑44地割
72        # 岩手県    和賀郡西和賀町  穴明22地割、穴明23地割
73        # 岩手県    九戸郡洋野町    種市第15地割~第21地割
74        $r[8] =~ s/(第)?(0|1|2|3|4|5|6|7|8|9)+地割.*$//s;
75
76        #『以下に掲載がない場合』等は削除してしまう
77        if ( $r[8] =~ /(^以下に掲載がない場合
78                        |の次に番地がくる場合
79                        |一円
80                        |)
81                        |、(.)*
82                        )$/xs ) {
83            $r[8] = "";
84            $r[5] = "";
85        }
86
87        # 郵便番号上位3桁
88        my $zip3 = ( $r[2] =~ /^([0-9]{3})/ )[0];
89
90        # 都道府県ID・市町村名・町域名のみ記録
91        $out->{$zip3} ||= {};
92        my $pref = int($r[0]/1000);
93        $out->{$zip3}->{$r[2]} ||= [ $pref, $r[7], $r[8] ];
94
95        # 都道府県が変わったら、画面に都道府県名を表示する
96        if ( $prev ne $pref ) {
97            $prev = $pref;
98            print STDERR " $c lines\n" if $c;
99            my $v = sprintf( "%s  \t", $r[6] );
100            $v = Encode::encode( $DISP_ENC, $v );
101            print STDERR $v;
102            $c = 0;
103        }
104        print STDERR "." if ( $c ++ % 200 == 0 );
105    }
106    print STDERR " $c lines\n" if ( $c > 0 );
107    close( CSV );
108    $out;
109}
110# ---------------------------------------------------------------- #
111# 事業所の個別郵便番号
112# http://www.post.japanpost.jp/zipcode/dl/jigyosyo/index.html
113# http://www.post.japanpost.jp/zipcode/dl/jigyosyo/lzh/jigyosyo.lzh
114# ---------------------------------------------------------------- #
115sub read_jigyosyo {
116    my $out      = shift or return;
117    my $csvjigyo = shift or return;
118    my $prev     = "";
119    my $c        = 0;
120
121    return unless ( -f $csvjigyo );
122
123    print STDERR "jigyosyo:\t$csvjigyo\n";
124    open( JIGYO, $csvjigyo ) or die "$! - $csvjigyo\n";
125    while ( my $iline = <JIGYO> ) {
126        last if ( $iline =~ /^\x1a/ );  # EOF
127
128        # UTF-8コードで処理する
129        $iline = Encode::decode( $CSV_ENC, $iline );
130
131        # CSVとはいっても「,」の文字は住所には利用されていないので簡易処理
132        my @r = split( ",", $iline );
133        s/^"(.*)"$/$1/s foreach ( @r );
134
135        # 第1・8・11・13カラムは、確実に数字のみのハズ
136        if ( $r[0]  !~ m#^\d{5}$# ||
137             $r[7]  !~ m#^\d{7}$# ||
138             $r[10] !~ m#^\d{1}$# ||
139             $r[12] !~ m#^\d{1}[\r\n]+$# ) {
140            die "Invalid Data Source: $csvjigyo (jigyosyo.csv)\n$iline\n";
141        }
142
143        # 全角かっこ『()』や『1F~9F』を削除
144        $r[6] =~ s/(.*)$//s;
145        $r[6] =~ s/(-)?((0|1|2|3|4|5|6|7|8|9)+(F|階|~|、)+)+
146                   (0|1|2|3|4|5|6|7|8|9)+(F|階)$//sx;
147
148        # 郵便番号上位3桁
149        my $zip3 = ( $r[7] =~ /^([0-9]{3})/ )[0];
150
151        # 都道府県ID・市町村名・町域名・番地を記録
152        $out->{$zip3} ||= {};
153        my $pref = int($r[0]/1000);
154        $out->{$zip3}->{$r[7]} ||= [ $pref, $r[4], $r[5], $r[6] ];
155
156        # 都道府県が変わったら、画面に都道府県名を表示する
157        if ( $prev ne $pref ) {
158            $prev = $pref;
159            print STDERR " $c lines\n" if $c;
160            my $v = sprintf( "%s  \t", $r[3] );
161            $v = Encode::encode( $DISP_ENC, $v );
162            print STDERR $v;
163            $c = 0;
164        }
165        print STDERR "." if ( $c ++ % 200 == 0 );
166    }
167    print STDERR " $c lines\n" if ( $c > 0 );
168    close( JIGYO );
169    $out;
170}
171# ---------------------------------------------------------------- #
172# 郵便番号上位3桁ごとにJSONファイルに書き出していく
173# ---------------------------------------------------------------- #
174sub write_json {
175    my $out  = shift or return;
176    my $prev = "";
177    my $c    = 0;
178
179    my $use_syck = $JSON::Syck::VERSION;
180    my $use_json = $JSON::VERSION unless $use_syck;
181    my $new_json = (( $use_json =~ /^([\d\.]+)/ )[0] >= 2.0 ) if $use_json;
182
183    print STDERR "module: \tJSON.pm ($use_json)\n" if $use_json;
184    print STDERR "module: \tJSON::Syck ($use_syck)\n" if $use_syck;
185    print STDERR "json:   \t$JSON_BASE\n";
186
187    foreach my $zip3 ( sort keys %$out ) {
188        # JSONフォーマットでダンプする
189        my $data = $out->{$zip3};
190        my $dump = $use_syck ? JSON::Syck::Dump($data) :
191                   $new_json ? to_json($data) : objToJson($data);
192
193        # JSONファイル名の決定
194        my $jsonfile = sprintf( $JSON_BASE, $zip3 );
195
196        # JSONファイル設置ディレクトリの確認
197        my $jsondir = ( $jsonfile =~ m#^(.*/)[^/]+$# )[0];
198        die "$! - $jsondir\n" if ( $jsondir && ! -d $jsondir );
199
200        # JSONファイルに書き出す
201        open( JSON, "> $jsonfile" ) or die "$! - $jsonfile\n";
202        $dump = Encode::encode( $JSON_ENC, $dump ) if $new_json;
203        print JSON $dump, "\n";
204        close( JSON );
205
206        # 郵便番号上位1桁が変わったら表示する
207        my $zip1 = ( $zip3 =~ /^([0-9])/ )[0];
208        if ( $prev ne $zip1 ) {
209            $prev = $zip1;
210            print STDERR " $c files\n" if $c;
211            printf STDERR ( "$JSON_BASE  ", "$zip1**" );
212            $c = 0;
213        }
214        print STDERR "." if ( $c ++ % 10 == 0 );
215    }
216    print STDERR " $c files\n" if ( $c > 1 );
217}
218# ---------------------------------------------------------------- #
219