2014年2月 : してログ

単純なワークシートのコピーで、下記のようなエラーが出ました。 コードを最小化しても出続けるみたいなので、試しに 1.7.8 にバージョンを下げてみたところ解決しました。 とりあえず、PHPExcel 1.7.9 は封印したほうが良いかも知れません。

Catchable fatal error: Argument 1 passed to PHPExcel_Cell::attach() must be an instance of PHPExcel_CachedObjectStorage_CacheBase, instance of PHPExcel_Worksheet given, called in C:\wk\bousai3\_cmn\PHPExcel_1.7.9\Classes\PHPExcel\CachedObjectStorage\Memory.php on line 99 and defined in C:\wk\bousai3\_cmn\PHPExcel_1.7.9\Classes\PHPExcel\Cell.php on line 115

エクセルのセル番号「A1」とか、アルファベットが列、数字が行というやつ。 アルファベットの部分をプログラムで扱う場合は、数字で管理しないとやりにくい。 そこで今日、その変換ルーチンを書いていたんですが、どうもうまくいかない。 二桁程度だとロジックも簡単なのだが、一般化しようと思って任意の桁に拡大した途端、難易度が段違いに上がる。 というか、断念してしまいました。

基本は、アルファベット26文字による26進数への変換となるはずなのですが、そうすると A~Z までは OK、その次が BA となる。 なぜかというと、ゼロが無いから。 よく考えると、ゼロのない26進数となっているのだが、アルファベット表記なため、それに気づきにくい。 話を簡単にするために、1~3 のゼロなし3進数とすると、1、2、3、11、12、13、21... というような並びになる。 従って、ゼロのある頭でロジックを考えた結果、Z の次が BA になる訳だ。

というところまで分かって、ゼロなしの26進数への変換ロジックを考えてみる。 これが、予想以上に難解で3桁の直前の YZ までできる関数しか出来ませんでした。 ので、ソースはありません。

代わりに、PHPExcel のスタティック関数(PHPExcel_Cell::stringFromColumnIndex)を使うことにしました。 PHPExcel のライブラリをインクルードすれば、静的関数なのですぐに使えます。 今度、ソースコードを覗いて、どんなロジックで変換しているのか勉強してみようと思います。

Windows7 の上位エディションには、仮想マシンで Windows XP を実行できるライセンスが付いています。 一般的な仮想マシンのようにウィンドウ内で Windows XP を実行する OS モードと、Windows7 のデスクトップ上で Windows XP のアプリケーションを実行できる仮想アプリケーションモードがあります。 で、その仮想アプリケーションモードでは、Windows XP に後からインストールしたアプリケーションしかメニューに出てこないため、最初から入っている Internet Explorer は利用できないのかと思っていました。 しかし、次のように手動で登録することにより、実行できることが分かりましたので紹介します。

  1. XP mode を OS モードで起動します
  2. 次のフォルダを開きます「C:\Documents and Settings\All Users\スタート メニュー\プログラム」
  3. Internet Explorer のショートカットを先のフォルダに作ります
  4. Windows7 のプログラムメニューに自動的に公開されます
  5. XP mode を終了します(仮想アプリケーションモードと共存できないため)

InternetExplorer 8 は、まだ要求仕様になることがあり、これならデバッグを効率的に行うことができます。 もちろん、自動更新を停止して IE6 や IE7 を Windows7 上で表示させることも可能です。

OpenLayers をバージョンアップしたところ、jQuery の draggable を適用したフローティング・パネルの動きが変になってしまいました。ゆっくりと動かして、ドラッグハンドルをマウスが外れなければ正しく動きますが、速く(といっても普通に)動かして、ドラッグハンドルをマウスが少しでも外れると、ドラッグが解除されたり、おかしな動作が始まります。マウスダウンからアップまで、ハンドリングできない状態になり、低スペックマシンではかなり操作しづらくなります。

なかなか原因が分からなくて、旧バージョンにロールバックしようかと思いましたが、分かってしまえば至って単純な原因でした。OpenLayers の新しいバージョンは、fallThrouth というオプションがデフォルトで false になっています。これを true にするだけでした。

	map = new OpenLayers.Map({
		fallThrough:       true
	});

FormData があると Ajax でファイルアップロードできるようになって大変便利なのですが、困るのが IE8 と IE9 の対応です。 IE10 からは FormData が使えますが、未だに IE8 の要求は来ます。 かつての IE6 のポジションを受け継いでいる臭が漂っています。

諦めて POST で画面リロードするところでしたが、ちゃんと調べて見ると代替策がありました。 使うのは POST ですが、見せ方としては非同期通信っぽく動きます。 サンプルコードは載せませんが、要点を箇条書きにしますので、参考にしてください。

  • 親ページに iframe を配置して見えないようにします(display:none だと Safari でまずいみたいなので、横幅&縦幅をゼロにします)
  • フォームのターゲット要素(target="<iframe名>")で、配置した iframe を指定します
  • サーバー側で POST を受け取ったら、JavaScript で親ページの関数を呼んで通知するようにします
  • 親ページでは関数が呼ばれたら、アップロード完了などのメッセージを提供できます

最近のブラウザは、showModalDialog で開いたウィンドウ内の遷移は普通に行えます。 しかし、IE8 では新しいウィンドウが開いてしまします。 ググると、iframe を入れて面倒なことして回避している輩もおられるようですが、そんなことしたくないですよね。 いろいろと調べてみると、海外のサイトに解決方法を見つけました。

IE8 はどうやらベースとなるターゲットが親ウィンドウかなんかに指定された状態で、ダイアログがオープンされているようです。 html のヘッダー部分に、ベース・ターゲットを自分自身にするよう指定すれば良いみたいです。

ベースターゲットの指定
<base target="_self" />

PHP でファイルを生成しブラウザにダウンロードさせるような処理なのですが、IE8 だけエラーが出てダウンロードできないようでした。 色々と調べてみると「Cache-Control: no-cache」が出ていると起こるようですので、下記のようにして対応しました。 最初は「Content-Disposition」に「attachment」を付けるとダメだと書いてあるサイトがあって悩みましたが、どうやら関係無いようです。

header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.urlencode($attachmentname));
header("Cache-Control: public");
header('Content-Length: '.strlen($dat));
echo $dat;

IE8 よ、XP と共に去れ

「オブジェクトでサポートされていないプロパティまたはメソッドです。」

この呪文の詠唱を IE が始めたら、とりあえずマウスぶん投げて甘いもんでも補給しましょう。 さて、今回は trim が原因なのですが、なんと IE8 は実装されていません。 このような基本的な、文字列操作が実装されていないなんて、はっきり言って思いもよりませんでした。 まさに(・・)です。

ならば、正規表現なんかを使って解決してもいいのですが、jQuery が使える状態なら、下記のようにすれば良いと思います。 少し前なら IE6 がー、とか言ってたけど、IE8 お前もなのか。。。 Microsoft は間違えなく、インターネットの発展の足を引っ張ってると思います。

IE8 で動かないコード
var after = before.trim();
jQuery での代替
var after = $.trim(before);

まさか、PDO のバグだったとは。

PostgreSQL に格納されたバイナリデータ(bytea)を PDO で取得しようとすると、文字列としてしか取得できないバグがあり、5.2.6 で報告されたのに、5.3.28 でも治っていないようです。 おかげで、急ぎのプロジェクトなのに、かなりの時間をロスしてしまいました。 通常、バイナリデータの取得は、以下のように書きますが、このバグにより「xffd8ffdb00430005030404040....」のような文字列が返ってきます。

$sth = $pgsql->prepare("select id,mime,img from t_image limit 1");
$sth->execute();
$sth->bindColumn('id', $id, PDO::PARAM_INT);
$sth->bindColumn('mime', $mime, PDO::PARAM_STR);
$sth->bindColumn('img', $img, PDO::PARAM_LOB);
if ($sth->fetch(PDO::FETCH_BOUND)) {
	header("Content-Type: {$mime}");
	fpassthru($img);
	exit;
}

PDO を使う限り解決策がないため、ネイティブ系の関数を使うか、文字列をバイナリ列に直すしか無いと思われます。 この文字列をバイナリ列に直すコードは、下記のようなものになります。

$sth = $pgsql->prepare("select id,mime,img from t_image limit 1");
$sth->execute();
$sth->bindColumn('id', $id, PDO::PARAM_INT);
$sth->bindColumn('mime', $mime, PDO::PARAM_STR);
$sth->bindColumn('img', $img, PDO::PARAM_LOB);
if ($sth->fetch(PDO::FETCH_BOUND)) {
	header("Content-Type: {$mime}");
	fgets($img,2);
	$data = fgets($img);
	echo pack('H*',$data);;
	exit;
}

文字列で返る以上、扱うデータ量は倍になりますし、pack という重めの関数を用いなければならないため、速度や効率面ではあまりよろしくありません。 ネイティブ系の関数で書ける場合は、そちらのほうがお勧めです。 なお、fgets($img,2); の部分は先頭に入る「x」の文字を捨てるためで、第2引数が「2」なのは、この関数が第2引数マイナス1バイトを読み込むという仕様のためです。 これに気づくのに、2時間ほど要してしまいました(^_^;)

PostgreSQL に接続中のユーザー名を取得する方法です。セッションユーザーは、データベースセッションを開始したユーザー、カレントユーザーは、現在の実行ユーザーとなります。スーパーユーザーでなければ、ユーザー変更できないので、通常は同じユーザーが入ります。

セッションユーザ名
select session_user;
現在の実行コンテキストのユーザ名
select current_user;

他にも、スキーマ名、IPアドレスなど様々な情報が取れますが、それらについてはこちらをご覧ください。ストアドプロシージャなどで活用すると、ログ取得などなど、色々と便利そうです。