XML (Perl)

XSLT で、属性(アトリビュート)の値を知りたい(Amazon A2S REST の Argument ノード)。

Amazon A2S REST の Argument ノードなんていってもチンプンカンプンだと思う。A2S とは、旧 Amazon Web Services のことで、A2S は Amazon Associates Web Service に 2007年11月 に改称されたために、Amazon Associates の A が 2つ重なるのをとって A2S というらしい。名前の改称自体は結構タイムリーなお知らせではある(笑)。まぁ、中身は変わらない。

続きを読む

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 のファイルの内容

<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>

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||; print qq||; print qq|

|; init(); print qq|

|; # 個々のデータのハンドラ 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"?>
<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 と同じ店。"
/>
...
続きを読む

Perl で、RSS で配信されたエントリーの日付を処理する

HTTP::Date クラスを使うと RSS で配信されたエントリーの日付 (dc:date) を変換してくれる。この場合、RSS のソースによって米国から日本のニュースにアクセスした場合などは、時差 (タイムゾーン) を考慮する必要があるため注意する。

使用するクラス


use HTTP::Date;
#!/usr/bin/perl use LWP::UserAgent; use HTTP::Request; use HTTP::Date; # 初期化 my $html; my $timezone = 'JST'; my $url = 'https://www.yoursite.com/index.rdf'; my $rss = new XML::RSS; my $proxy = new LWP::UserAgent; # UseAgent の作成 my $req = new HTTP::Request(GET=>$url); my $res = $proxy->request($req); # $url にアクセスする my $xml = $res->content; # コンテンツ (この場合は RSS/XML) を取得 # RSS を解析 eval {
$rss->parse($xml);
}; if($@) {
# $rss->parse が失敗したとき
} # @{$rss->{items}} に、RSS のすべてのエントリー (item) が入る foreach my $item ( @{$rss->{items}} ) {
my $date        = HTTP::Date::str2time($item->{dc}->{date}, $timezone);

$html    .=    qq|<a href=$item->{link}>$item->{title}</a><br>\n|; # タイトルをリンクつきで出力
$html    .=    qq|$description<br>|    if $item->{description};    # 概要 (description) を出力
$html    .=    qq|[<a href=$item->{link}>続きを読む</a>]<hr size=1>\n|;
} print $html; 1;
続きを読む

Perl で、文字コードの自動判定をする

たとえば、LWP::UserAgent で取得した RSS (= XML ファイル) の文字コードの自動判定 (エンコーディングの自動判定) を行うには、以下のようにする。

#!/usr/bin/perl use Encode::Guess; use LWP::UserAgent; use HTTP::Request; my $url = 'https://perltips.twinkle.cc/index.rdf'; my $proxy = new LWP::UserAgent; my $req = new HTTP::Request(GET=>$url); my $res = $proxy->request($req); my $xml = $res->content; my $enc = guess_encoding($xml, qw/euc-jp shiftjis 7bit-jis utf8/); ref($enc) or die "Can't guess: $enc"; # 自動判定に失敗したときのエラー処理 $xml = decode($enc->name, $xml); # utf8 で保存 ...
続きを読む

Perl で、アップロードしたファイルを処理する

Perl の場合は、CGI として動作させる場合は、必ず


print qq|Content-type: text/html\n\n|;

という行が必要な以外は、PHP とほとんど同じである。 アップロードには、いつも使っているおなじみの CGI モジュールを使う。


use CGI;

そうすると、以下のようにアクセスできる。


our $CGI = new CGI;
...
my $xml = $CGI->param('file');

$xml はファイルポインタが入っている。通常は open(IN, "..."); などするときの IN にあたるものである。以下は、コード。アップロードする側の HTML ファイルは PHP でアップロードファイルを処理するで紹介したのと同じ。

#!/usr/bin/perl use strict; use CGI; our $CGI = new CGI; print qq|Content-type: text/html\n\n|; init(); 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);
} 1;
続きを読む
XML (Perl) を購読