扶桑ゴム産業の最新情報とうぇぶますたあ「TKYK」の個人的日記。 ゴムペディアゴム通扶桑ゴム産業 株式会社扶桑ゴム産業

calendar

S M T W T F S
    123
45678910
11121314151617
18192021222324
252627282930 
<< June 2017 >>

categories

archives

広告

PDFをPerlで生成する時の苦労話

 もうちょっとでeカットシステムを公開できそうなので、それにまつわる苦労話でも書いてみよう。今回の要求仕様の一つに、「見積もりPDFを生成してユーザーが印刷したり出来るようにする」というものがあった。

PDFは最初外注先に一任しようと思っていたのだが、あれ?あれれれ?みたいな感じで、いつの間にかこちらで何とかして生成せねばならなくなってしまった(苦笑)<だって、画像貼り付けでPDF、みたいなこと言われてしまったし…

まあ、スキルが上がるのはよし!(前向き)ということで、調査しているとさすがCPAN、既にPerlだけでPDFを作成することが出来るではないか。モジュールを読み込んで、実装することが出来た。簡単な差し込み印刷のようなものだったから、それはそれなりに。

でも、だんだんと欲がでるもの。欲といっても、より分かりやすく、お客さんが間違えない様なものであって欲しいという欲。型番を出力するときに、文字列が、ランダムチックなので、「1」「I」「l」「0」「O」「o」の判別が容易なフォントの採用を考えた。これも無事に出来て良かった良かった!と思ったのもつかの間、なんか変だ…

コピペしてテキスト情報としてエディタに貼り付けると文字化けする!なぜ??

原因は結局分からず、フォント埋め込みはなしになった。その代わり、間違えやすい文字を使わないという仕様に変更した。

今日、某紙面PDFの日本語での検索が出来ないという話を聞いて、ちょっと調べてみた。現象が似ているからだ。

どうやら、TrueTypeフォントの一部を埋め込んだ場合、うまくコピペできないPDFになってしまうらしい。アドビの説明によれば、

大体結論は、
この問題を解決するには、日本語に小塚ゴシックや小塚明朝などの OpenType フォントを使用します。
というくだり。そうか、そうなのか…学ぶ事って無限ですね…。
PDFを見られるときは、苦労したんだねと思い出してくだされば幸いです(^^;;


あら、いつの間にかSearchMashが終わってるじゃないですか…

 グリーンページのサイト検索の裏であくせく働いてくれていたGoogle謹製の実験サイト「SearchMash」がいつの間にかサービス終了していた。うわぁ、それでエラーが…。ここのJSONも使えなくなった今、どこに向かえばいいのか…とほほ

とりあえずほとんどはクロールキャッシュでまかなっている状態なので、表示が全く出ないことはないんだけど、手が空き次第、他力本願型(笑)次世代エンジン(またかよ)に更新しようと…思うけど、どうなるかなぁ。


漢数字をアラビア数字にスマートに変換する

Yu君から「漢数字とアラビア数字の相互変換」のお題をもらう。久々にPerlをいじくったなって感じ。調べたらモジュール(Lingua::JA::Numbers)が存在していて、変換自体は簡単にできる。しかし、文章中から漢数字を抽出することは比較的敷居が高い。

最初正規表現で抽出したが、ソース中の日本語文字列は、use utf8しないと、バイト単位で扱おうとするので変になっていた。調べたら、既に前に同じようなことがあってメモっていた…。

use utf8;
とすると、記述したコードはUTF8フラグが付いたものとなる。よって、

 my $word;
 utf8::decode($word); #UTF8フラグをつけて、文字単位で処理
 {
  use utf8;
  $word =~ s/([一二三四五六七八九十百千万億兆京]{1,70})/kanji2number($1)/egg;
 }

のように、明示して必要な時に用いることができる。


参考
Perl 5.8.x Unicode関連
iRSSの日記 - UTF-8がからむ文字化け解決

この問題は解決したが、文章中の「一方」とかも「1方」になったりしていた。正規表現だけではこれは難しい。

そこで、さらに品詞分解をMeCabに行わせて、数の部分のみを変換対象とするようにした。

以下、スマートでもビューティフルでもないソース。
#! /usr/bin/perl

use strict;
use CGI;
use Unicode::Japanese;
use Text::MeCab;
use Lingua::JA::Numbers;
use List::MoreUtils qw( mesh );
my $q = new CGI;
my $unijp = Unicode::Japanese->new();
my $mecab = Text::MeCab->new();

# 送られてきたデータを処理する -----------------
my $word = $q->param("word");
my $type = $q->param("option");
my $parse = $q->param("parse");
my @FEATURE_NAMES = ( '品詞', '品詞細分類1', '品詞細分類2', '品詞細分類3',
           '活用形', '活用型', '原形', '読み', '発音' );

print $q->header(  '-Content-Type' => 'text/html',
     '-charset'   => 'UTF-8',
    );

$word = $unijp->set($word)->z2hNum->get;

if ($type eq 'ja2num') {
 
 if ($parse eq 'mecab') {
  my $number_flag = 0;
  my @words;
  my $numword;
  for ( my $node = $mecab->parse( $word ); $node; $node = $node->next ) {
   if (length $node->surface) {
    if ( proc( $node )->{'品詞細分類1'} eq '数') { #数だったら
     $number_flag = 1;
     $numword .= $node->surface; #数の文字列をしまい込み
    }
    else {
     if ($number_flag == 1) { #数の文字列が終わったので変換作業へ
      push (@words , kanji2number($numword));
      $number_flag = 0;
      $numword = '';
     }
     push ( @words , $node->surface );
    }
   }
  }
  $word = join ('', @words );
 }
 else { #正規表現
  utf8::decode($word);
  use utf8;
  $word =~ s/([一二三四五六七八九十百千万億兆京]{1,70})/kanji2number($1)/egg;
 }
}
else {
 $word =~ s/([0-9,]{1,9})/number($1)/egg;
}

print "結果は以下の通りです。
";
print $q->textarea(
 -name => 'after',
 -rows => 10,
 -cols => 70,
 -default => $word,
);

sub number {
 my $num = shift || '0';
 $num =~ s/,//g; #カンマを削除
 $num *= 1; #いらない小数点を消す
 $num = num2ja($num, {style => $type});
 utf8::encode($num);

 return $num;
}

sub kanji2number {
 my $kanji = shift;
 print "(".$kanji.")";
 utf8::decode($kanji);
 my $num = ja2num($kanji, {style => 'kanji'});
 my ($i, $j);
 if ($num =~ /^[-+]?¥d¥d¥d¥d+/g) {
  for ($i = pos($num) - 3, $j = $num =~ /^[-+]/; $i > $j; $i -= 3) {
   substr($num, $i, 0) = ',';
  }
 }
 return $num;
}

sub proc {
 my ($node) = @_;
 my @features = split /,/, $node->feature;
 return { mesh( @FEATURE_NAMES, @features ) };
}


あとは、HTMLも適当に
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html;CHARSET=UTF-8">
<TITLE>漢数字←→アラビア数字とかに変換するスクリプト</TITLE>
</HEAD>
<BODY>
<h1>漢数字←→アラビア数字とかに変換するスクリプト</h1>


<FORM ACTION="Numbers-convert.cgi" METHOD="POST"
ENCTYPE="application/x-www-form-urlencoded" name="form">
<h3>元のデータ</h3><br>
(全角数字は半角に変換します。)<br>
<textarea NAME="word" rows="10" cols="70"></textarea><BR>
<h3>変換文字:</h3><br>
<input type="radio" name="option" value="ja2num" id="op0" checked>
<label for="op0">漢数字→アラビア数字</label><br>
 <input type="radio" name="parse" value="mecab" id="mecab" checked>
<label for="mecab">MeCab【品詞分解】</label>
<input type="radio" name="parse" value="re" id="re">
<label for="re">正規表現</label><br>
<input type="radio" name="option" value="kanji" id="op1">
<label for="op1">アラビア数字→漢数字</label><br>
<input type="radio" name="option" value="romaji" id="op2">
<label for="op2">アラビア数字→ローマ字</label><br>
<input type="radio" name="option" value="hiragana" id="op3">
<label for="op3">アラビア数字→ひらがな</label><br>
<input type="radio" name="option" value="katakana" id="op4">
<label for="op4">アラビア数字→カタカナ</label><br>
<br>
<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="変換!"><BR>
</FORM>
</BODY>
</HTML>


とかして、変換してみた。

変換前:

【ワシントン13日共同】米政府と連邦準備制度理事会(FRB)は十三日夕、サブプライム住宅ローン問題の影響で業績が悪化している政府系住宅金融二社、連邦住宅抵当金庫(ファニーメイ)と連邦住宅貸付抵当公社(フレディマック)に対する緊急支援声明を発表した。両社の経営問題が世界の金融市場の重大関心事となっているため、危機回避へ日曜日の異例の支援表明になった。

 FRB声明によると、両社の短期的な資金繰りを支援するため、必要に応じてニューヨーク連邦準備銀行を通じて公定歩合で融資する。

 米メディアによると、フレディマックは資金調達のために三十億ドル(約三千二百億円)の債券発行を十四日に控えており、経営不安説が高まる中で支障なく資金が集められるかが焦点になっている。

 両社の経営問題が前週末の米欧株価の下落を呼び、週明けの東京市場への波及が懸念されていたため、市場の沈静化を目的に異例の対応になったとみられる。

 米紙ウォールストリート・ジャーナル(電子版)によると、両社が保有・保証している住宅ローン関連の証券化商品は約五兆二千億ドルと、米国の同証券化商品の半分近くを占める。

 一方、英紙サンデー・タイムズは十三日付で、市場における両社の信用を回復させるため、米政府が最大で百五十億ドルの資本注入を検討している、と伝えた。

 両社は、政府の住宅政策を担う重要な機関だが民間上場企業。報道内容が実現すれば、米政府が相当数の株式を保有することになる。

七面鳥
二面性
九死に一生スペシャル
五万二千ペソ
七転八倒
九章三節
山田一二三



で、変換後。

【ワシントン13日共同】米政府と連邦準備制度理事会(FRB)は13日夕、サブプライム住宅ローン問題の影響で業績が悪化している政府系住宅金融2社、連邦住宅抵当金庫(ファニーメイ)と連邦住宅貸付抵当公社(フレディマック)に対する緊急支援声明を発表した。両社の経営問題が世界の金融市場の重大関心事となっているため、危機回避へ日曜日の異例の支援表明になった。

 FRB声明によると、両社の短期的な資金繰りを支援するため、必要に応じてニューヨーク連邦準備銀行を通じて公定歩合で融資する。

 米メディアによると、フレディマックは資金調達のために3,000,000,000ドル(約320,000,000,000円)の債券発行を14日に控えており、経営不安説が高まる中で支障なく資金が集められるかが焦点になっている。

 両社の経営問題が前週末の米欧株価の下落を呼び、週明けの東京市場への波及が懸念されていたため、市場の沈静化を目的に異例の対応になったとみられる。

 米紙ウォールストリート・ジャーナル(電子版)によると、両社が保有・保証している住宅ローン関連の証券化商品は約5,200,000,000,000ドルと、米国の同証券化商品の半分近くを占める。

 一方、英紙サンデー・タイムズは13日付で、市場における両社の信用を回復させるため、米政府が最大で15,000,000,000ドルの資本注入を検討している、と伝えた。

 両社は、政府の住宅政策を担う重要な機関だが民間上場企業。報道内容が実現すれば、米政府が相当数の株式を保有することになる。

七面鳥
二面性
九死に一生スペシャル
52,000ペソ
七転八倒
9章3節
山田一二三


詰めが甘いと思いますが、おおむねいい感じです。
この変換により、桁違いな話であることを実感できるツールになりそうです。

サイボウズ「メールワイズ」の添付ファイル名について

Mail::Senderによるメール送信も何とか出来るようになってきたが、日本語ファイル名による添付メールを送信する際、
Content-type: text/plain; name="1190945591_18426.txt"
Content-transfer-encoding: Base64
Content-disposition: attachment; filename="=?ISO-2022-JP?B?GyRCJGEkSRsoQi50eHQ=?=";

のように、MIMEエンコードしたものをContent-dispositionにつけてやる事が多いように思う。Outlookとかもそうだけど、規定された仕様はどうか。

添付ファイルにおける日本語のファイル名に関して
http://www.emaillab.org/essay/japanese-filename.html

ここを見ると、この実装は間違いであって、本当はRFC 2231に基づくエンコードである必要がある。というか、この実装をしているメーラーってあるのか?びっくりちょっとテストしたところ、この正しい記述で送ったメールをメーラで見ると、無視されている(苦笑)(でもThunderBirdはしっかり対応してました。流石!!)

ま、こんな状態だから長いものに巻かれておこうか…。

でも、サイボウズの「メールワイズ」はなぜか
Content-type: text/plain; name="1190945591_18426.txt"

こっちのContent-typeに記述されているファイル名を表示してしまうので、何とかならんかなと思う。この仕様、あんまり良くないと思うのですが、如何でしょう??

何とかこっちでしたい気分だったが、如何せんMail::Senderの(正しい)仕様故に、モジュールをいじるのもなんですのでそのままにしちゃおう。うんうん。そうだ、出来るところは手抜きだ!イエイ!

Mail::SenderのFrom欄に日本語があると送信できなかったりする件

お手軽メール送信モジュールMail::SenderのFrom欄にて、日本語文字列が長いとエラーになってしまう件。色々やってなかなか分からなかったがようやく解けた。理由は、「改行」にあった。
my $sender = new Mail::Sender{
   ...
   debug=> '/webroot/debug-mail.txt',
   ...
};

と、デバッグモードでSMTPに送信している内容を確認してみた。
すると、
<< MAIL FROM:

などとなっているべき部分がやはりおかしくなっていて、
<< MAIL FROM: <?ISO-2022-JP?B?GyRCPHc4Qkw1PHc4Qkw1OGU4dyROJD?=
hoge@hoge.com>

のようになっていた。つまり、純粋なメールアドレスを送るべき部分がきちんと送られていないということになる。
そこで、Mail::SenderモジュールSender.pmを実際に覗いてみると(880,887行目)、
$self->{'fromaddr'} = $self->{'from'};
$self->{'fromaddr'} =~ s/.*<([^¥s]*?)>/$1/ if ($self->{'fromaddr'}); # get from email address

のようになっていた。$self->{'fromaddr'}が実際のアドレスで、$self->{'from'}が、"テスト株式会社 <info@hogehoge.co.jp>"といった文字列にあたる部分(もちろん実際はエンコード必要)である。

テストプログラムでは、
my $from = jcode($config{mailfrom})->mime_encode;

のようにJcodeに投げてMIMEエンコードしているため、一定文字数で改行コードが入る。すると、
s/.*<([^¥s]*?)>/$1/

は、不完全な処理となってしまうので、上のような現象が起きてしまっていた。
解決するには、この部分などを、
s/.*<([^¥s]*?)>/$1/s

として、「.*」(任意の文字列)に改行コードを含めるようにすればいいが、モジュールをいじるのはあまり良くないので、テストプログラム側で、
my $from = jcode($config{mailfrom})->mime_encode;
$from =~ s/¥n//g; #改行が悪影響を及ぼすため消去

として、改行コードを除去してから渡すようにした。これにより、無事に長い文字列でもメール送信することができることを確認できた。

それにしても、Subjectはともかく、FromやTo欄の場合は、MIMEエンコードに改行を入れるのはやはりNGなのだろうか。まともに守られていない(ように思える)仕様を守るのは結構面倒なので適当になっちゃってますが…。

CGIとmod_perlの動作の違いに関して

CGIとmod_perlの動作の違いに関して。

CGIプログラミング
CGIプログラミング

p458〜460による言葉を引用すれば、
コードは一度にコンパイルされキャッシュされます。そのため、スクリプトのボディにあるレキシカル変数がサブルーチンの中でアクセスされるとクロージャが作成されます。一般的なCGIスクリプトでは次のようなことができます。

 my $q = new CGI;

 check_input( );
 .
 .
 sub check_input {
  unless ( $q->param( "email" ) ) {
   error( $q, "電子メールアドレスが入力されていません" );
  }
  .
  .

このスクリプトでは、check_inputにはCGIオブジェクトを渡していません。それなのに、変数はサブルーチンからは見えています。...CGIでは問題なく機能しますが、mod_perlでは非常に難解で厄介なエラーが発生してしまいます。
-問題は、最初にApacheの子プロセスでスクリプトが実行される際に、CGIオブジェクトの値がキャッシュされたcheck_inputのコピーにトラップされることです。次回以降の同じApacheの子プロセスへの呼び出しはすべて、check_input内の以前のCGIオブジェクトの値が再利用されます
-この問題は、$qをパラメタとしてcheck_inputに渡すか、または$qをレキシカル変数からグローバルなlocal変数に変更することで解決できます。

テストもしてみたが、サブルーチン内の$qは最初以外は2回目以降も同じ値(つまりキャッシュされている)で、サブルーチンの外(スクリプトのボディ)だと、毎回値が変わっているので、このあたりを頭に入れないといけないということ。簡単な方法は、myをourにすればいい。

でも今悩んでいるのは、mod_perlではうまく動くのに、普通のCGI動作だと変な動作をする件。あれこれ悩んで、上の記事も見て、実験したり試行錯誤やってもどうも変。電子の大きさになって調べたい気分。

本件と全く関係ないがさっき思いついたイメージを言葉で表わすと:焼肉換算。
お、あと、パチっとくる、だった(意味不明だ)



Cache::FileCacheでキャッシュ

前回のGoogle検索結果取得ルーチンにキャッシュ機能を追加してみた。

use Cache::FileCache;
my $cache = new Cache::FileCache( ¥%cache_opt );
--
my $google_response = $cache->get($google_json_url);
   # キャッシュを呼んでみる
if (!$google_response) { #キャッシュがなかった
  $google_response = $ua->get($google_json_url);
    # 投げて返してもらう
}

my $google_results = $json->decode($google_response->content);

if ($google_results->{estimatedCount} > 0) {
   #1件以上のデータがあったら
  $cache->set($google_json_url, $google_response);
   #キャッシュにしまう
}

今のところ、いい感じです楽しい

追記:
いい感じだったハズだったのですが、ローカルと違いなぜかエラーログが…。
ということで戻しちゃいました(泣)原因究明病棟24時。

追記2:
原因は、どうやらメソッドへのハッシュデータをそっくり保存していたため、キャッシュ読み込み時におかしくなっていたようです。
ということで、以下のように修正。


my $google_response = '';
my $google_results = $cache->get($keycode); # キャッシュを呼んでみる

if (!$google_results) { #キャッシュがなかった
  $is_cache = 'not cached';
  $google_response = $ua->get($google_json_url); # 投げて返してもらう
  $google_results = $json->decode($google_response->content);
}

my $total_count = $google_results->{estimatedCount} || '';#だいたいの総件数
$total_count =~ s/,//g; #カンマは削除する

if ($total_count > 0) { #1件以上のデータがあったら
  $cache->set($keycode, $google_results); #キャッシュにしまう
}


追記3:
「メソッドへのハッシュデータをそっくり保存していたため」もちょっとあやしい度30%。Class::DBIなど他の場合はうまくいくような??よくわからんのぉ。ま、うまくいったからよしとすべきか。(いいかげんだなぁ)

Google SOAP APIが終わった今…次なるAPIはJSON?!

SOAPが終わって見捨てられて悲しくって涙ふきふきしてる場合じゃない。
ということで、代替策を考えてみた。一つはYahoo!検索API。もひとつはGoogle直取得。前者は早速やってみた。

んー、結構便利に出来てるし、しかも割と簡単に利用できるのは有り難い。でも、ほとんどクロールしてないので利用はとりあえずパス。

じゃ後者は、といえば、ちょっとめんどくさいな…と思っていて探していたら、「SearchMash の未公開 JSON API」という記事が。おー!!
ジェイソンジェイソンですよ。悲鳴が聞こえそうだ。(って読み方調べたら本当にジェイソンなんだ…)

で、ぼちぼち早速実装

耐油性 耐熱性 耐候性 耐水性 耐寒性 耐摩耗性 薄いゴム 極薄ゴム ウエットスーツ素材 低反発ウレタン 防振ゴム 制振材 免震 防音材 防音シート エジソンのお箸 …これぐらいにしとこイヒヒ

これが初めてのWebサービスとの連携になるでしょうか…。いい感じです。でも、アクセスが多くなるとどうやらはねるようなので、ちょっと常時検索というのはクールでないような気がします。ということで、もちっと本気に実装する際にはキャッシュ機能も付けてみようかと思います。

Google SOAP API、終わっていた!?

気づいたら、いつの間にかGoogleのSOAP利用の検索APIが終了していた。今作っているβ検索に、と思っていたのに…。気づくの遅すぎ〜。

ということは、代替策となるのは…

ちょっとだけMeCabチューン

とりあえずインストールしていたMeCabだったが、バグ?もありありで、なんか意欲が失せていたのだが、最近0.94にバージョンアップして、各種バグFixも行なわれたようなので、早速ユーザー辞書をいじってみた。
context_id.cpp(88) [it != left_.end()] cannot find LEFT-ID for 名詞,固有名詞,一般,*,*,*,*

みたいな表示も出て、またかー!と思ったけど、なんとか切り抜けた。
nkf --utf8 left-id.def > left-id.1
rm left-id.def
mv left-id.1 left-id.def

みたいな感じで{left|right}-id.defの文字コードを合わせてやることと、品詞の指定を正しいものにしておくこと。(文字コードの件は間違ってるかも、自信なし)
# /usr/local/libexec/mecab/mecab-dict-index ¥
-d/usr/local/lib/mecab/dic/ipadic -u user.dic -f utf8 -t utf8 user.csv
reading user.csv ... 3
emitting double-array: 100% |###################################|

done!

ちょっとだけ、切り分けが賢くなった!
これで、「ネオロン」が「ネオ ロン」というマヌケな結果にならないですみます楽しい

| 1/4PAGES | >>