HTML5の要素を条件付きコメントなしでIEで使う
今更ながら、ここもHTML5にしてみようかなという気持ちが湧いてきています。以前は、一旦公開した文書の形式を無闇に変えるのはよくないという思いが強かったのですが、個人のページなのだし色々と実験的なことをもっと試してもいいのでは、と思うようになったので。
さて、HTML5導入にあたり一番気にしていたのが、IEでHTML5の新しい要素を使う方法のことでした。今だと html5shiv を使って以下のようなコメントを入れるのが一般的になっているようです。
<!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]-->
しかしながら、HTML文書というのは様々な環境で利用されるための文書形式だと自分は考えているので、その中に(コメントとは言え)特定のブラウザのための記述を入れることには抵抗があったのでした。そこで、上記のような条件付きコメントを使わずに、IEでHTML5の新しい要素を使う方法をここ数日模索しており、何とか方法が見つかりましたので以下に紹介する次第です。
ひとまずサンプルをどうぞ。
- HTML5 Shiv HTC test (html5htc.html)
他に使用しているファイルは以下の通りです。
見ての通り、以前に角丸の表示で利用していた、IEの DHTML Behaviors を利用しました。これだとHTML文書に適用するCSSの中で、
body {
behavior:url(./html5.htc);
}
というようなルールを追加するだけなので、HTML5文書自体には余計な記述を追加することなく利用できます。
なお、html5.htc の中では html5shiv のコードをほぼそのまま利用しています。
この html5.htc を作る過程の試行錯誤で、2点ほど学んだことがあったので覚え書き。
htcファイルの中から、HTML文書のdocumentオブジェクトは直接参照できない。 htcファイルのscript要素で document と書くと、それは「htcファイルのdocumentオブジェクト」ということになるようです。一方、behavior を適用した要素(body要素)は element で参照できるので、HTML文書の document オブジェクト、window オブジェクトを得るには以下のようにする必要がありました。
var doc = element.document;
var win = doc.parentWindow;
最後に element.innerHTML の更新が必要。 これをしなくてもhtcファイルのscript要素のスクリプトは実行されていたので、それで問題ないように思っていたのですが、実際はそれだけだとスタイルが適用されませんでした。色々試す中で、半ば自棄で element.innerHTML = element.innerHTML というコードを入れてみたところ、思いがけずそれでうまく行ってしまったのでした。おそらく代入によりHTML5新要素のタグが改めて解析されたのだろうと推測しますが、自身を代入することで結果が変わるというのは何とも不思議な感じです。
コードをより短くするため、element.innerHTML += '' と変更しました。
Mojolicious wikiにあるORLiteの例をUnicode対応にする
Mojolicious::LiteでData::Modelを使ってみたという記事で、
MojoliciousのWikiにORLiteを使ったサンプルがあったのですが、残念ながらそのままでは日本語には対応していないので、…
という文を見かけたので、その Working with ORLite inside Mojolicious にあるサンプルを日本語でも使えるようにできないか、調べてみました。
Mojolicious::Liteにおける文字エンコーディング周りの動作を調べてみたところ、Mojolicious::Liteの内部では、文字列はあくまでPerlの内部文字列として扱われることを知りました。具体的に言うと:
- GET または POST されたデータは、自動的に内部文字列に変換される。
- テンプレートデータは内部文字列として保持され、出力時にまとめて特定の文字エンコーディング(UTF-8等)に変換される。
なので、 $self->param('type') のようにして取得したデータは、その時点で内部文字列となっているし、また、データベースから取得したデータをテンプレートに埋め込むならば、内部文字列とした上で埋め込む必要がある、ということになります。
そうすると修正はデータベース寄りということになりそうです。調べると、ORLiteが使用するDBD::SQLiteには、入出力データを内部文字列として扱うための属性(sqlite_unicode)がありました。しかしORLiteではその属性を使用していないようです。
そこで、以下のように package Model のところで connect メソッドを上書きすることで、日本語文字列のポスト・表示にも対応することができました。
追記(2012年2月3日): ORLite がバージョン1.52以降で unicode オプションをサポートしたため、以下のソースも変更しました。
#!/usr/bin/env perl
package Model;
use strict;
use ORLite {
file => 'sample.db',
create => sub {
my $dbh = shift;
$dbh->do(
'CREATE TABLE motorcycles (
id INTEGER PRIMARY KEY,
type TEXT NOT NULL,
brand TEXT NOT NULL,
color TEXT)'
);
# just use $dbh->do(...) if you need more
# tables
return 1;
},
# ORLite >= 1.52
unicode => 1,
};
#{
# # use unicode character (ORLite < 1.52)
# my $connect = sub {
# DBI->connect( $_[0]->dsn, undef, undef, {
# PrintError => 0,
# RaiseError => 1,
# sqlite_unicode => 1,
# } );
# };
# no warnings 'redefine';
# *connect = $connect;
#}
package main;
use Mojolicious::Lite;
use utf8;
get '/' => sub {
my $self = shift;
$self->stash(motorbikes => [Model::Motorcycles->select('order by type')]);
} => 'index';
post '/' => sub {
my $self = shift;
Model::Motorcycles->create(
type => $self->param('type'),
brand => $self->param('brand'),
color => $self->param('color')
);
$self->redirect_to('/');
};
app->start;
__DATA__
@@ index.html.ep
<!DOCTYPE html>
<html>
<head>
<title>Motorcycles</title>
<style>td { background-color:#dee; } </style>
</head>
<body>
<h1>Motorcycles</h1>
<table>
% foreach my $cycle (@{$motorbikes} ) {
<tr>
<td><%= $cycle->type %></td>
<td><%= $cycle->brand %></td>
<td><%= $cycle->color %></td>
</tr>
% }
</table>
<p>Enter a new motorcycle here (バイクを登録して下さい)</p>
<p>
<%= form_for '/' => (method => 'post') => begin %>
% foreach (qw/type brand color/) {
<%= uc($_) %>: <%= input_tag $_, 'type' => 'text' %><br />
% }
<%= submit_button 'Submit motorcycle' %>
<% end %>
</p>
</body>
</html>
スクリプト内部(__DATA__ 以下)のテンプレートで日本語文字列を使うような場合は、内部文字列として扱われるように use utf8; を追加しておくとよさそうです。
weakenは何に対して効くか
PerlのScalar::Util にある weaken について学んでいたところ、誤解していた点があったので覚え書き。
以下のように、同じリファレンス値を持つ変数を2つ作り、片方だけを weaken することを考えます。
use strict;
use warnings;
use feature qw(say);
use Scalar::Util qw(weaken isweak);
my ($a, $b);
$a = $b = {};
weaken $a;
say "\$a = $a is ", isweak $a ? 'weak' : 'strong';
say "\$b = $b is ", isweak $b ? 'weak' : 'strong';
自分は、$aも$bもリファレンスとしては同じ値なのだから、$aをweakenすれば$bも弱くなる、という風に思っていたのですが、結果は以下の通りでした。
$ perl a.pl $a = HASH(0xc43ad8) is weak $b = HASH(0xc43ad8) is strong
リファレンスの値としては同じにも関わらず、$aのみが弱くなっています。
これはつまりどういうことかと考えたところ、weakenはHASH(0xc43ad8)というようなリファレンスとしての値に対して働くのではなく、リファレンスを格納する変数名(のようなもの)に対して働くものなのだ、と解釈すると納得が行きました。
※「変数名(のようなもの)」の例:
- スカラ変数であれば、
$scalar - 配列の要素であれば、
$array[0] - ハッシュの要素であれば、
$hash{key} - 配列リファレンスの要素であれば、
$array_ref->[0] - ハッシュリファレンスの要素であれば、
$hash_ref->{key}
引数の値ではなく変数名に対して働くということで言うと、weaken は my や local に似ているように思いました。
もう少し実際の利用例に近い形で言うと、例えば以下のように、互いに参照し合うオブジェクトがあり、一方の参照を弱くすることでブロック終端でオブジェクトの変数が開放されることを示す例を考えます。(コードは404 Blog Not Found:perl - Data::Decycle で悪循環を断とう!の記事にあったものを元にしてます)
use strict;
use warnings;
use feature qw(say);
use Scalar::Util qw(weaken);
{
package Foo;
sub new { bless {}, $_[0] }
sub DESTROY { warn "destroy $_[0]" }
}
{
package main;
my $parent = Foo->new;
my $child = Foo->new;
$parent->{child} = $child;
$child->{parent} = $parent;
weaken $child->{parent};
}
warn "end";
__END__
$ perl a.pl
destroy Foo=HASH(0xc43c70) at a.pl line 9.
destroy Foo=HASH(0xc9b510) at a.pl line 9.
end at a.pl line 21.
で、このweakenの行において、$child->{parent} = $parent だからと言って weaken $parent としては意味がない、ということです。
なお、この辺りのことはScalar::UtilのPODにも以下のように説明されていました。
Note that if you take a copy of a scalar with a weakened reference, the copy will be a strong reference.
my $var; my $foo = \$var; weaken($foo); # Make $foo a weak reference my $bar = $foo; # $bar is now a strong reference
インストールされているPerlモジュールはinstmodshで見れる
Perlで、インストールされているモジュールの一覧を取得するには ExtUtils::Installed を使うとよいという記事をよく見かけていたので、自分もよくそのようにしていました。
しかし本日、perlbrewでperl-5.12.2をインストールした後、bin/ 配下に置かれるコマンドをなんとなく眺めていたところ、instmodsh というコマンドがあることに気付きました。これを使うと、
$ instmodsh Available commands are: l - List all installed modules m <module> - Select a module q - Quit the program cmd? l Installed modules are: App::cpanminus HTML::Parser HTML::Tagset LWP Perl Time::HiRes URI cmd? q $
という感じで、簡単なシェルインターフェースを介してインストールされたモジュールの一覧を得ることができます(上記はまだperlインストール直後なので出力が少ないですが)。
ソースを見たところ、こちらも内部では ExtUtils::Installed を使用しているようなのですが、それでも perl -MExtUtils::Installed -le 'print for ExtUtils::Installed->new->modules' といった、ExtUtils::Installedという長いモジュール名を2回も書くような onliner を思い出しつつ打つよりは楽でよいと思いました。
ちなみに、UIを介さずに直接一覧を出力したりファイルにリダイレクトさせたい場合には、以下のようにするとよいかと思います。
$ echo -e "l\nq" | instmodsh > instmod.log
最初のメッセージやプロンプトを削りたいのなら、こんな感じで。
$ echo -e "l\nq" | instmodsh | perl -ne 'next if 1 .. /^c/; print if /^\s/'
検索した限りだとこの instmodsh について言及した日本語記事はあまり見かけない様子で、折角デフォルトでperlに入っているのに、知られていないのは勿体無いなと思ったのでした。
メソッドを局所的に上書きする
知ってそうで意外と知られていないperlの小技 10選という記事にある「モジュールのメソッドを上書きする」という項について考えてみました。記事にあるやり方では、
- 本来のメソッド(コード)のリファレンスを保存
- 型グロブによりメソッドを上書き
- 上書きしたメソッドを実行
- 保存していたコードリファレンスを更に上書き
となっていたのですが、こうするよりも、上書きが必要な箇所のみをブロック({ ... })で囲み、その中でlocalを使って上書きする方がよいのでは、と思ったのです。具体的に書くと以下の通り。
use strict;
use warnings;
#----------
package Foo;
sub something { print "something original.\n" };
#----------
package main;
Foo->something();
{
no warnings 'redefine';
local *Foo::something = sub { print "something overwitten.\n" };
Foo->something();
}
Foo->something();
実行結果は以下の通り。
something original. something overwitten. something original.
こうすると本来のメソッドのリファレンスを保存する手間も省けるし、メソッド上書きと no warnings 'redefine' の効果の範囲も分かり易いのではないかと思います。
……ということを考えていたのですが、後日、Re: 知ってそうで意外と知られていないperlの小技 10選という別の記事で更に別のやり方が提示されていました。
事前にundefすると、 no warnings 'redefine' しなくとも警告は出ません。
use strict; use warnings; sub do_something { ... } undef &do_something; *do_something = sub { print "Implemented.\n" }; do_something;
サブルーチンをundefで未定義にできる、ということを知らなかったのでなるほどと思ったのですが、これをブロック内のlocal上書きでもできるだろうか、というところが気になり、試してみました。
use strict;
use warnings;
#----------
package Foo;
sub something { print "something original.\n" };
#----------
package main;
Foo->something();
{
undef &Foo::something;
local *Foo::something = sub { print "something overwitten.\n" };
Foo->something();
}
Foo->something();
結果は以下の通り。
something original. something overwitten. Undefined subroutine &Foo::something called at b.pl line 18.
ブロック内であってもundefの効果はグローバルに及んでしまうので、本来のメソッドを保持できなくなっていました。
しかしlocalで宣言だけするなら、宣言した変数は未定義になることを思い出し、
local *Foo::something;
*Foo::something = sub { print "something overwitten.\n" };
とすればよいのでは、と思いつきました。これは一応意図通りに動いたのですが、型グロブでまとめてlocal宣言するということは、同じ名前を持つスカラ・配列・ハッシュ等の変数にも効果が及ぶということでもあります。以下のコードを試してみました。
use strict;
use warnings;
#----------
package Foo;
sub something { print "something original.\n" };
our $something = 'SOME STRING';
#----------
package main;
Foo->something();
print $Foo::something, "\n";
{
local *Foo::something;
*Foo::something = sub { print "something overwitten.\n" };
Foo->something();
print $Foo::something, "\n";
}
Foo->something();
print $Foo::something, "\n";
結果は以下の通り。
something original. SOME STRING something overwitten. Use of uninitialized value $Foo::something in print at b.pl line 19. something original. SOME STRING
メソッドと同じ名前のスカラ変数($Foo::something)もまとめて未定義にされていました。なのでlocal宣言と上書きを分けて書くのは注意が必要かもしれません。
ちなみに、local宣言と同時にコードリファレンスを代入してやると、他の変数が上書きされるようなことにはなりませんでした。動作としてはやや不思議な気もしますが、これが一番問題の無い書き方のように思います。
use strict;
use warnings;
#----------
package Foo;
sub something { print "something original.\n" };
our $something = 'SOME STRING';
#----------
package main;
Foo->something();
print $Foo::something, "\n";
{
no warnings 'redefine';
local *Foo::something = sub { print "something overwitten.\n" };
Foo->something();
print $Foo::something, "\n";
}
Foo->something();
print $Foo::something, "\n";
結果は以下の通り。ブロック内でも$Foo::somethingの値は保持されていました。
something original. SOME STRING something overwitten. SOME STRING something original. SOME STRING
関連: