Google Maps API で、地図上に経路 / ルート (パス) を表示する

これは、Perl で、Goolge Maps からルートを検索する方法 に対するブラウザ側の処理の例である。以下のコードを参考にして欲しい。repaint メソッドの中でパスを描画している。前提としては以下の通り。
  1. このスクリプトは呼び出し元の HTML から your_ajax_script.js としてリンクされている。
  2. このスクリプトが呼ばれる HTML には、
    があるとする。
  3. init メソッドは、HTML の onLoad から呼ばれるものとする。
your_ajax_script.js の例
//<![CDATA[

var BASE_URL = 'https://yourdomain.com';
var CGI_FILE = '/path_to_cgi_dir/your.cgi';
var DEFAULT_ZOOM = 5;

var map;
var currentPoint;

// init は、HTML の onLoad から呼ばれるものとする。
function init() {

    currentPoint = new GPoint(-122.13930130004883, 37.40248284497232);
    map = new GMap(document.getElementById('map'));

    GEvent.addListener(map, 'moveend', function() {
        // add your code here
    });

    GEvent.addListener(map, 'click', function(o, p) {
        // add your code here
    });

    GEvent.addListener(map, 'infowindowopen', function() {
        // add your code here
    });

    GEvent.addListener(map, 'infowindowclose', function() {
        // add your code here
    });

    map.setMapType(G_MAP_TYPE);
    map.centerAndZoom(currentPoint, DEFAULT_ZOOM);
    map.addControl(new GLargeMapControl());
    map.addControl(new GMapTypeControl());
    map.addControl(new GScaleControl());
}

function repaint(xml) {

    var list_html = '';

    if(xml) {

        // IE の場合はより厳密にチェックが必要、Firefox は xml だけでよい
        if(xml.documentElement) {

            var path = xml.documentElement.getElementsByTagName('path');

            var points = [];
            for (var i = 0; i < path.length; i++) {
                points.push(new GPoint( path[i].getAttribute('lng'),
                                        path[i].getAttribute('lat')));
            } // End of for

            var route = xml.documentElement.getElementsByTagName('route');
            list_html += route.length > 0 ? '<ol>' : '';
            for (var i = 0; i < route.length; i++) {
                list_html += '<li>' + route[i].getAttribute('description') + '</li>';
            } // End of for
            list_html += route.length > 0 ? '</ol>' : '';
        }
    }

    // このスクリプトが呼ばれる HTML には、<div id="list"></div> があるとする。
    document.getElementById('list').innerHTML =  list_html;
    map.addOverlay(new GPolyline(points));
}

// getPath メソッドは、マウスでボタンが押された、などのイベントに反応する
function getPath(lng, lat) {

    currentPoint = new GPoint(lng, lat);

    post(    'do=path'
        +    '&toX=' + lng
        +    '&toY=' + lat);
}

// post メソッドは、例えば getPath メソッドから呼ばれる
function post(data) {

    var xmlHttp = GXmlHttp.create();

    xmlHttp.onreadystatechange = function() {

        if (xmlHttp.readyState == 4) {
            var xml = xmlHttp.responseXML;
            repaint(xml);
        }
    }

    xmlHttp.open('POST', BASE_URL + CGI_FILE, true);
    xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xmlHttp.send(data);
}

//]]>
これは、Perl で、Goolge Maps からルートを検索する方法 に対するブラウザ側の処理の例である。以下のコードを参考にして欲しい。repaint メソッドの中でパスを描画している。前提としては以下の通り。
  1. このスクリプトは呼び出し元の HTML から your_ajax_script.js としてリンクされている。
  2. このスクリプトが呼ばれる HTML には、
    があるとする。
  3. init メソッドは、HTML の onLoad から呼ばれるものとする。
your_ajax_script.js の例
//<![CDATA[

var BASE_URL = 'https://yourdomain.com';
var CGI_FILE = '/path_to_cgi_dir/your.cgi';
var DEFAULT_ZOOM = 5;

var map;
var currentPoint;

// init は、HTML の onLoad から呼ばれるものとする。
function init() {

    currentPoint = new GPoint(-122.13930130004883, 37.40248284497232);
    map = new GMap(document.getElementById('map'));

    GEvent.addListener(map, 'moveend', function() {
        // add your code here
    });

    GEvent.addListener(map, 'click', function(o, p) {
        // add your code here
    });

    GEvent.addListener(map, 'infowindowopen', function() {
        // add your code here
    });

    GEvent.addListener(map, 'infowindowclose', function() {
        // add your code here
    });

    map.setMapType(G_MAP_TYPE);
    map.centerAndZoom(currentPoint, DEFAULT_ZOOM);
    map.addControl(new GLargeMapControl());
    map.addControl(new GMapTypeControl());
    map.addControl(new GScaleControl());
}

function repaint(xml) {

    var list_html = '';

    if(xml) {

        // IE の場合はより厳密にチェックが必要、Firefox は xml だけでよい
        if(xml.documentElement) {

            var path = xml.documentElement.getElementsByTagName('path');

            var points = [];
            for (var i = 0; i < path.length; i++) {
                points.push(new GPoint( path[i].getAttribute('lng'),
                                        path[i].getAttribute('lat')));
            } // End of for

            var route = xml.documentElement.getElementsByTagName('route');
            list_html += route.length > 0 ? '<ol>' : '';
            for (var i = 0; i < route.length; i++) {
                list_html += '<li>' + route[i].getAttribute('description') + '</li>';
            } // End of for
            list_html += route.length > 0 ? '</ol>' : '';
        }
    }

    // このスクリプトが呼ばれる HTML には、<div id="list"></div> があるとする。
    document.getElementById('list').innerHTML =  list_html;
    map.addOverlay(new GPolyline(points));
}

// getPath メソッドは、マウスでボタンが押された、などのイベントに反応する
function getPath(lng, lat) {

    currentPoint = new GPoint(lng, lat);

    post(    'do=path'
        +    '&toX=' + lng
        +    '&toY=' + lat);
}

// post メソッドは、例えば getPath メソッドから呼ばれる
function post(data) {

    var xmlHttp = GXmlHttp.create();

    xmlHttp.onreadystatechange = function() {

        if (xmlHttp.readyState == 4) {
            var xml = xmlHttp.responseXML;
            repaint(xml);
        }
    }

    xmlHttp.open('POST', BASE_URL + CGI_FILE, true);
    xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xmlHttp.send(data);
}

//]]>

パスワードは生のままデータベースに保存してはいけない?

いわゆるログイン処理において、ユーザーがウェブページのフォームから入力してきたユーザー名とパスワードをデータベースに保存されているものと照合し、認証するという仕組みはメンバー制サイトにはもはや欠かせないものとなっている。ここで、ユーザー名とパスワードは SSL で暗号化された通信路(チャネル)を通じてサーバに送るのは当然として、データベースにユーザー名とパスワードを生のまま入れていた場合は、もしデータベースが何者かにハッキングされたらパスワードがそのまま漏洩してしまうことになる。パスワードというものはある個人であればどのサイトでも共通にしていることも多いのではないだろうか? あなたはまさにこのエントリを読んでいるくらいなのだからそんなことはないかも知れないが、一般ユーザーのセキュリティに対するリテラシーはどうだろう?そのようなことを前提にすると、仮に(あってはならないことだが)自サイトからパスワードが漏れた場合、そのパスワードを使って悪意のあるものが次々に他のサイトへの認証を試みるシナリオもあると思う。また、データベースがハッキングされないまでも、管理者がデータベースを覗けばユーザーが設定したパスワードを簡単に見ることができるシステムは、やはりセキュリティ上問題であろう。 このような事態を避けるためにも、セキュリティの基本ではあるが、データベース中のパスワードはハッシュ文字列で保存しておくべきである。たとえデータベースの管理者がデータベースに保存されているハッシュ値を覗けたとしても、そのハッシュ値からのパスワード自体の復元はほぼ不可能だからだ。このような仕組みにすると、ユーザーがパスワードを忘れた場合はパスワードで照合できなくなるので、再設定の仕組みが必要である。このポイントを頭に入れて注意深く世のサイトがどのように作られているか、普段からアンテナをはって参考にして欲しい。 この方法は特に Perl だけにできるということではなく、もちろん言語を問わず、ハッシュ関数が用意されている Java や PHP にも使える。
いわゆるログイン処理において、ユーザーがウェブページのフォームから入力してきたユーザー名とパスワードをデータベースに保存されているものと照合し、認証するという仕組みはメンバー制サイトにはもはや欠かせないものとなっている。ここで、ユーザー名とパスワードは SSL で暗号化された通信路(チャネル)を通じてサーバに送るのは当然として、データベースにユーザー名とパスワードを生のまま入れていた場合は、もしデータベースが何者かにハッキングされたらパスワードがそのまま漏洩してしまうことになる。パスワードというものはある個人であればどのサイトでも共通にしていることも多いのではないだろうか? あなたはまさにこのエントリを読んでいるくらいなのだからそんなことはないかも知れないが、一般ユーザーのセキュリティに対するリテラシーはどうだろう?そのようなことを前提にすると、仮に(あってはならないことだが)自サイトからパスワードが漏れた場合、そのパスワードを使って悪意のあるものが次々に他のサイトへの認証を試みるシナリオもあると思う。また、データベースがハッキングされないまでも、管理者がデータベースを覗けばユーザーが設定したパスワードを簡単に見ることができるシステムは、やはりセキュリティ上問題であろう。 このような事態を避けるためにも、セキュリティの基本ではあるが、データベース中のパスワードはハッシュ文字列で保存しておくべきである。たとえデータベースの管理者がデータベースに保存されているハッシュ値を覗けたとしても、そのハッシュ値からのパスワード自体の復元はほぼ不可能だからだ。このような仕組みにすると、ユーザーがパスワードを忘れた場合はパスワードで照合できなくなるので、再設定の仕組みが必要である。このポイントを頭に入れて注意深く世のサイトがどのように作られているか、普段からアンテナをはって参考にして欲しい。 この方法は特に Perl だけにできるということではなく、もちろん言語を問わず、ハッシュ関数が用意されている Java や PHP にも使える。

Perl で、Excel のデータ (XML) をアップロードして処理する方法

  1. まず、Excel で 以下のように category1、category2、name、address、phone、link、comment1、comment2 というカラム構成のワークシートを作成し、適当にデータを入力、XML 形式で保存しておく。注: 住所は米国の住所でないと正しく処理されないことに注意
  2. 次に、以下にあるように gmap_excel.html と gmap_excel.cgi の 2つのファイルを用意する (.gmap_excel.cgi 中のパスの設定などは適宜変更のこと)。
  3. gmap_excel.html から上の XML ファイルをサーバにアップロードする。
  4. サーバ側では Excel の XML データを処理して、gmap_excel.xml というファイルが出力される。
  5. このサーバ側で出力された gmap_excel.xml ファイルは Google Maps API を使った AJAX クライアントから利用できる。
gmap_excel.html のファイルの内容
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <link rel="stylesheet" href="styles-site.css" type="text/css" />
    </head>
    <body>
        <form enctype="multipart/form-data" action="gmap_excel.cgi" method="post">
            <input type="hidden" name="MAX_FILE_SIZE" value="2000000" />
            <table width="600">
                <tr><td>Names file:</td><td><input type="file" name="file" />
                        <input type="submit" value="Upload" /></td></tr>
            </table>
        </form>
    </body>
</html>
gmap_excel.cgi のファイルの内容
#!/usr/bin/perl

use lib "path_to_your_perl_lib/lib/perl5/site_perl";

use strict;
use Geo::Google;
use CGI;

our $XML_HEADER   = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";  #  XML ヘッダ
our $HOME_DIR     = 'your_home_dir';
our $LOCK_DIR     = 'path_to_lock_dir/lock'; # do chmod 777 path_to_lock_dir/lock
our $LOCK_FILE    = 'lock';
our $XML_FILE     = 'path_to_your_output_xml/gmap_excel.xml';        #  出力用 XML ファイル名

our $col_index    = 0;  #  Excel シートの現在の列(カラム)インデックス
our $row_index    = 0;  #  Excel シートの現在の行インデックス
our @data         = (); #  出力用データ
our @current_data = (); #  現在のデータ

# オブジェクト初期化
our $CGI = new CGI;
our $GEO = new Geo::Google;

# HTML 出力
print qq|Content-type: text/html\n\n|;
print qq|<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />|;
print qq|<link rel="stylesheet" href="styles-site.css" type="text/css" />|;
print qq|<div class="content"><p>|;
init();
print qq|</p></div>|;

# 個々のデータのハンドラ
sub dataHandler {

    my ($expat, $text) = @_;

    #  $row_index = 0 と 1 は、Excel で データのヘッダのため必要なし
    if($text !~ /^\s+$/ && $row_index > 1) {    #  空白行でなければ
        $current_data[$col_index] = $text;
    }
}

# 要素の開始
sub startElement {

    my ($expat, $tag, %attr) = @_;

    if($tag eq 'Row') {            #  <Row>で始まるとき

        $col_index = 0;                #  カラムを初期化
        $row_index++;                #  行を進める
        @current_data = ();            #  現在の行データを初期化
    }

    if($tag eq 'Data') {            #  <Data>で始まるとき

        $col_index++;                #  列を進める
    }
}

# 要素の終了
sub endElement {

    my ($expat, $tag) = @_;
    my ($xml, $html);
    my ($lng, $lat, $name, $link, $category1, $category2);
    my ($street, $city, $state, $phone, $comment1, $comment2);

    #  </Row> のとき
    if($tag eq 'Row') {

        push(@data, {    # データに現在の行データを代入

            'category1' => $current_data[1],
            'category2' => $current_data[2],
            'name'      => $current_data[3],
            'address'   => $current_data[4],
            'phone'     => $current_data[5],
            'link'      => $current_data[6],
            'comment1'  => $current_data[7],
            'comment2'  => $current_data[8]
        });
    }

    #  </Workbook> のとき
    if($tag eq 'Workbook') {

        $row_index = -1; #  行数を初期化 ($row_index = 0 はヘッダのため -1 からスタート)
        foreach my $row (@data) {

            $row_index++;

            if($row->{address}) {
                $lng = '';  $lat = '';
                my ($degree) = getDegrees($row->{address});
                if($degree) {
                    $lng    = $degree->longitude;
                    $lat    = $degree->latitude;
                }
            }

            $name      = $row->{name};
            $link      = $row->{link}
                       =~ /http:\/\/[!#-9A-~]+\.+[a-z0-9]/ ? $row->{link} : '';
            $category1 = $row->{category1};
            $category2 = $row->{category2};
            ($street, $city, $state) = split(',', $row->{address});
            $street    = $street;
            $city      = $city;
            $state     = $state;
            $phone     = $row->{phone};
            $comment1  = $row->{comment1} eq '-' ? '' : $row->{comment1};
            $comment2  = $row->{comment2};

            #  lng と lat が検索できたら (データが入っているということだから)
            if($lng && $lat) {

                $xml    .= "\t<marker\n"
                        .  "\t\tlng       = \"$lng\" lat = \"$lat\"\n"
                        .  "\t\tname      = \"" . $name      . "\"\n"
                        .  "\t\tlink      = \"" . $link      . "\"\n"
                        .  "\t\tcategory1 = \"" . $category1 . "\"\n"
                        .  "\t\tcategory2 = \"" . $category2 . "\"\n"
                        .  "\t\taddress1  = \"$street\"\n"
                        .  "\t\taddress2  = \"$city, $state\"\n"
                        .  "\t\tphone     = \"$phone\"\n"
                        .  "\t\tcomment1  = \"$comment1\"\n"
                        .  "\t\tcomment2  = \"$comment2\"\n"
                        .  "\t/>\n\n";

            } elsif($row_index) {

                $html   .= '<tr><td>'
                        .  "$row_index / $name / $street, $city, $state"
                        .  '</td></tr>';
            }
        }

        $xml    =  $XML_HEADER
                .  "<markers>\n"
                .  $xml
                .  "</markers>\n";

        if($html) {    #  エラーがあれば
            $html    = '<table>'
                     . '<tr><td>***** 以下、緯度経度が検索不能でした。</td></tr>'
                     . $html
                     . '</table>';
        } else {    #  エラーがなければ
            print '<br />done.';
        }

        #  ファイルのオープン
        &file_lock;        #  ファイルロック
        open(XML, ">$HOME_DIR$XML_FILE")
        or die('Cannot open an XML file: ' . "$HOME_DIR$XML_FILE");
        print XML $xml;    #  書き込み
        close(XML);        #  ファイルを閉じる
        &file_unlock;      #  ロック解除

        print $html;
    }
}

sub getDegrees {    #  住所から緯度経度を検索

    return $GEO->location( address => shift); # shift = address / リファレンスを返す
}

sub init() {

    # XMLパーサー作成 ('UTF-8')
    my $xml_parser = new XML::Parser(   ProtocolEncoding => 'UTF-8',
                                        Handlers=>{ Start=>\&startElement,
                                                    End  =>\&endElement,
                                                    Char =>\&dataHandler });
    # ファイルのオープンはいらない。input タグで指定した name 属性がそのままファイルハンドルになる
    my $xml = $CGI->param('file');

    # XMLパース処理
    $xml_parser->parse($xml) or    die "XML error: $xml_parser";

    # ファイルクローズ
    close($xml);
}

sub file_lock {
    my $wait = 5;
    while (!symlink('.',"$HOME_DIR$LOCK_DIR/.$LOCK_FILE")) {
        if (--$wait <= 0) {
#            &error(    '現在他の方が使用中です。',
#                    'しばらくしてから再度ご利用ください。');
        }
        sleep (1);
    }
}

sub file_unlock {
    unlink ("$HOME_DIR$LOCK_DIR/.$LOCK_FILE");
}

1;
gmap_excel.xml 出力例
<?xml version="1.0" encoding="UTF-8"?>
<markers>
    <marker
        lng       = "-122.125690" lat = "37.413570"
        name      = "Tofu House"
        link      = ""
        category1 = "Restaurant"
        category2 = "Korean"
        address1  = "4127 El Camino Real"
        address2  = " Palo Alto,  CA 94306"
        phone     = "650-424-8805"
        comment1  = ""
        comment2  = "Santa Clara の Tofu House と同じ店。"
    />

...

</markers>
  1. まず、Excel で 以下のように category1、category2、name、address、phone、link、comment1、comment2 というカラム構成のワークシートを作成し、適当にデータを入力、XML 形式で保存しておく。注: 住所は米国の住所でないと正しく処理されないことに注意
  2. 次に、以下にあるように gmap_excel.html と gmap_excel.cgi の 2つのファイルを用意する (.gmap_excel.cgi 中のパスの設定などは適宜変更のこと)。
  3. gmap_excel.html から上の XML ファイルをサーバにアップロードする。
  4. サーバ側では Excel の XML データを処理して、gmap_excel.xml というファイルが出力される。
  5. このサーバ側で出力された gmap_excel.xml ファイルは Google Maps API を使った AJAX クライアントから利用できる。
gmap_excel.html のファイルの内容
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <link rel="stylesheet" href="styles-site.css" type="text/css" />
    </head>
    <body>
        <form enctype="multipart/form-data" action="gmap_excel.cgi" method="post">
            <input type="hidden" name="MAX_FILE_SIZE" value="2000000" />
            <table width="600">
                <tr><td>Names file:</td><td><input type="file" name="file" />
                        <input type="submit" value="Upload" /></td></tr>
            </table>
        </form>
    </body>
</html>
gmap_excel.cgi のファイルの内容
#!/usr/bin/perl

use lib "path_to_your_perl_lib/lib/perl5/site_perl";

use strict;
use Geo::Google;
use CGI;

our $XML_HEADER   = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";  #  XML ヘッダ
our $HOME_DIR     = 'your_home_dir';
our $LOCK_DIR     = 'path_to_lock_dir/lock'; # do chmod 777 path_to_lock_dir/lock
our $LOCK_FILE    = 'lock';
our $XML_FILE     = 'path_to_your_output_xml/gmap_excel.xml';        #  出力用 XML ファイル名

our $col_index    = 0;  #  Excel シートの現在の列(カラム)インデックス
our $row_index    = 0;  #  Excel シートの現在の行インデックス
our @data         = (); #  出力用データ
our @current_data = (); #  現在のデータ

# オブジェクト初期化
our $CGI = new CGI;
our $GEO = new Geo::Google;

# HTML 出力
print qq|Content-type: text/html\n\n|;
print qq|<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />|;
print qq|<link rel="stylesheet" href="styles-site.css" type="text/css" />|;
print qq|<div class="content"><p>|;
init();
print qq|</p></div>|;

# 個々のデータのハンドラ
sub dataHandler {

    my ($expat, $text) = @_;

    #  $row_index = 0 と 1 は、Excel で データのヘッダのため必要なし
    if($text !~ /^\s+$/ && $row_index > 1) {    #  空白行でなければ
        $current_data[$col_index] = $text;
    }
}

# 要素の開始
sub startElement {

    my ($expat, $tag, %attr) = @_;

    if($tag eq 'Row') {            #  <Row>で始まるとき

        $col_index = 0;                #  カラムを初期化
        $row_index++;                #  行を進める
        @current_data = ();            #  現在の行データを初期化
    }

    if($tag eq 'Data') {            #  <Data>で始まるとき

        $col_index++;                #  列を進める
    }
}

# 要素の終了
sub endElement {

    my ($expat, $tag) = @_;
    my ($xml, $html);
    my ($lng, $lat, $name, $link, $category1, $category2);
    my ($street, $city, $state, $phone, $comment1, $comment2);

    #  </Row> のとき
    if($tag eq 'Row') {

        push(@data, {    # データに現在の行データを代入

            'category1' => $current_data[1],
            'category2' => $current_data[2],
            'name'      => $current_data[3],
            'address'   => $current_data[4],
            'phone'     => $current_data[5],
            'link'      => $current_data[6],
            'comment1'  => $current_data[7],
            'comment2'  => $current_data[8]
        });
    }

    #  </Workbook> のとき
    if($tag eq 'Workbook') {

        $row_index = -1; #  行数を初期化 ($row_index = 0 はヘッダのため -1 からスタート)
        foreach my $row (@data) {

            $row_index++;

            if($row->{address}) {
                $lng = '';  $lat = '';
                my ($degree) = getDegrees($row->{address});
                if($degree) {
                    $lng    = $degree->longitude;
                    $lat    = $degree->latitude;
                }
            }

            $name      = $row->{name};
            $link      = $row->{link}
                       =~ /http:\/\/[!#-9A-~]+\.+[a-z0-9]/ ? $row->{link} : '';
            $category1 = $row->{category1};
            $category2 = $row->{category2};
            ($street, $city, $state) = split(',', $row->{address});
            $street    = $street;
            $city      = $city;
            $state     = $state;
            $phone     = $row->{phone};
            $comment1  = $row->{comment1} eq '-' ? '' : $row->{comment1};
            $comment2  = $row->{comment2};

            #  lng と lat が検索できたら (データが入っているということだから)
            if($lng && $lat) {

                $xml    .= "\t<marker\n"
                        .  "\t\tlng       = \"$lng\" lat = \"$lat\"\n"
                        .  "\t\tname      = \"" . $name      . "\"\n"
                        .  "\t\tlink      = \"" . $link      . "\"\n"
                        .  "\t\tcategory1 = \"" . $category1 . "\"\n"
                        .  "\t\tcategory2 = \"" . $category2 . "\"\n"
                        .  "\t\taddress1  = \"$street\"\n"
                        .  "\t\taddress2  = \"$city, $state\"\n"
                        .  "\t\tphone     = \"$phone\"\n"
                        .  "\t\tcomment1  = \"$comment1\"\n"
                        .  "\t\tcomment2  = \"$comment2\"\n"
                        .  "\t/>\n\n";

            } elsif($row_index) {

                $html   .= '<tr><td>'
                        .  "$row_index / $name / $street, $city, $state"
                        .  '</td></tr>';
            }
        }

        $xml    =  $XML_HEADER
                .  "<markers>\n"
                .  $xml
                .  "</markers>\n";

        if($html) {    #  エラーがあれば
            $html    = '<table>'
                     . '<tr><td>***** 以下、緯度経度が検索不能でした。</td></tr>'
                     . $html
                     . '</table>';
        } else {    #  エラーがなければ
            print '<br />done.';
        }

        #  ファイルのオープン
        &file_lock;        #  ファイルロック
        open(XML, ">$HOME_DIR$XML_FILE")
        or die('Cannot open an XML file: ' . "$HOME_DIR$XML_FILE");
        print XML $xml;    #  書き込み
        close(XML);        #  ファイルを閉じる
        &file_unlock;      #  ロック解除

        print $html;
    }
}

sub getDegrees {    #  住所から緯度経度を検索

    return $GEO->location( address => shift); # shift = address / リファレンスを返す
}

sub init() {

    # XMLパーサー作成 ('UTF-8')
    my $xml_parser = new XML::Parser(   ProtocolEncoding => 'UTF-8',
                                        Handlers=>{ Start=>\&startElement,
                                                    End  =>\&endElement,
                                                    Char =>\&dataHandler });
    # ファイルのオープンはいらない。input タグで指定した name 属性がそのままファイルハンドルになる
    my $xml = $CGI->param('file');

    # XMLパース処理
    $xml_parser->parse($xml) or    die "XML error: $xml_parser";

    # ファイルクローズ
    close($xml);
}

sub file_lock {
    my $wait = 5;
    while (!symlink('.',"$HOME_DIR$LOCK_DIR/.$LOCK_FILE")) {
        if (--$wait <= 0) {
#            &error(    '現在他の方が使用中です。',
#                    'しばらくしてから再度ご利用ください。');
        }
        sleep (1);
    }
}

sub file_unlock {
    unlink ("$HOME_DIR$LOCK_DIR/.$LOCK_FILE");
}

1;
gmap_excel.xml 出力例
<?xml version="1.0" encoding="UTF-8"?>
<markers>
    <marker
        lng       = "-122.125690" lat = "37.413570"
        name      = "Tofu House"
        link      = ""
        category1 = "Restaurant"
        category2 = "Korean"
        address1  = "4127 El Camino Real"
        address2  = " Palo Alto,  CA 94306"
        phone     = "650-424-8805"
        comment1  = ""
        comment2  = "Santa Clara の Tofu House と同じ店。"
    />

...

</markers>

Perl で、データベースを使う

Perl で、データベース (ここでは MySQL を使うときの基本) をおさらいしよう。Perl でのデータベースへのアクセスは、DBI モジュールを使う。このモジュールは XS で書かれているため、Linux などにインストールするには gcc のコンパイル環境が必要である。レンタルサーバは予め DBI がインストールされているところを選んでおけば間違いないが、そうでない場合は自力でインストールする必要があるため注意しよう。 さて、以下はデータベース中のテーブルのフィールドが id、name、value となっていてそれらを扱う場合の例である。最初のデータベースの定義のところで、mysql のところを Pg とするだけで PostgreSQL に対応できる (ようである)。 スクリプト中ではデータベースのテーブルを変数でマッピング (定義) している (ハッシュを使う方法もあると思うのでもっといい方法があったら教えて欲しい)。こうしておくと後でテーブルの定義を変更したときなど柔軟に対応できるだろう。 foreach の中の「$_」は、Perl では「現在処理中のデータ」であり、
foreach my $data (@$DATA) {
    $id    $data->$T_FIELD[$T_ID];
    $name  $data->$T_FIELD[$T_NAME];
    $value $data->$T_FIELD[$T_VALUE];
}
などするのと同義である。 DBI->selectrow_arrayref は、selectrow_array と似ているが、結果をリファレンスで複数行返すところが異なる。リファレンスなのでその後の処理も高速である (と思われる)。 使用するモジュール
use DBI;
#!/usr/bin/perl

use DBI;

# データベースの設定
our $DB_NAME     = 'DBI:mysql:your_database_name';
our $DB_USERNAME = 'your_username';
our $DB_PASSWORD = 'your_password';
our $DB_TABLE    = 'your_table_name';

# テーブルの定義
our $T_ID    = 0;
our $T_NAME  = 1;
our $T_VALUE = 2;

our @T_FIELD = (
    '`id`',
    '`name`',
    '`value`'
);

# データベースに接続
my $DB = DBI->connect($DB_NAME, $DB_USERNAME, $DB_PASSWORD)
       || die "Connection error";

# データを検索
my $DATA = $DB->selectall_arrayref(
                            qq|SELECT * FROM `$DB_TABLE` |
                          . qq|WHERE $T_FIELD[$T_ID]='ここに検索したい ID を書く'|
                        );

# データがあれば
if(@$DATA)    {

    my $id, $name;

    # データを出力
    foreach (@$DATA)    {

        $id    = $_->[$T_ID   ];    # $T_ID    = 0
        $name  = $_->[$T_NAME ];    # $T_NAME  = 1
        $value = $_->[$T_VALUE];    # $T_VALUE = 2

        print "$id / $name / $value\n";      # データを表示
    }
}

# データベースの接続を切断
$DB->disconnect();

1;
Perl で、データベース (ここでは MySQL を使うときの基本) をおさらいしよう。Perl でのデータベースへのアクセスは、DBI モジュールを使う。このモジュールは XS で書かれているため、Linux などにインストールするには gcc のコンパイル環境が必要である。レンタルサーバは予め DBI がインストールされているところを選んでおけば間違いないが、そうでない場合は自力でインストールする必要があるため注意しよう。 さて、以下はデータベース中のテーブルのフィールドが id、name、value となっていてそれらを扱う場合の例である。最初のデータベースの定義のところで、mysql のところを Pg とするだけで PostgreSQL に対応できる (ようである)。 スクリプト中ではデータベースのテーブルを変数でマッピング (定義) している (ハッシュを使う方法もあると思うのでもっといい方法があったら教えて欲しい)。こうしておくと後でテーブルの定義を変更したときなど柔軟に対応できるだろう。 foreach の中の「$_」は、Perl では「現在処理中のデータ」であり、
foreach my $data (@$DATA) {
    $id    $data->$T_FIELD[$T_ID];
    $name  $data->$T_FIELD[$T_NAME];
    $value $data->$T_FIELD[$T_VALUE];
}
などするのと同義である。 DBI->selectrow_arrayref は、selectrow_array と似ているが、結果をリファレンスで複数行返すところが異なる。リファレンスなのでその後の処理も高速である (と思われる)。 使用するモジュール
use DBI;
#!/usr/bin/perl

use DBI;

# データベースの設定
our $DB_NAME     = 'DBI:mysql:your_database_name';
our $DB_USERNAME = 'your_username';
our $DB_PASSWORD = 'your_password';
our $DB_TABLE    = 'your_table_name';

# テーブルの定義
our $T_ID    = 0;
our $T_NAME  = 1;
our $T_VALUE = 2;

our @T_FIELD = (
    '`id`',
    '`name`',
    '`value`'
);

# データベースに接続
my $DB = DBI->connect($DB_NAME, $DB_USERNAME, $DB_PASSWORD)
       || die "Connection error";

# データを検索
my $DATA = $DB->selectall_arrayref(
                            qq|SELECT * FROM `$DB_TABLE` |
                          . qq|WHERE $T_FIELD[$T_ID]='ここに検索したい ID を書く'|
                        );

# データがあれば
if(@$DATA)    {

    my $id, $name;

    # データを出力
    foreach (@$DATA)    {

        $id    = $_->[$T_ID   ];    # $T_ID    = 0
        $name  = $_->[$T_NAME ];    # $T_NAME  = 1
        $value = $_->[$T_VALUE];    # $T_VALUE = 2

        print "$id / $name / $value\n";      # データを表示
    }
}

# データベースの接続を切断
$DB->disconnect();

1;