IE で a 要素を使って相対 URL からスキームを得る2014年11月09日 20時22分

いくつかのブラウザではURLがhttpの相対リンクであってもelm.protocolが「:」や空文字になったりする

JavaScriptでリンク先URLがhttp/httpsか確認する方法 - 葉っぱ日記

この「いくつかのブラウザ」とは IE のことを指していると思われますが、href プロパティに絶対 URL を設定しなおすことで、URL のスキーム部分を取得できるようになります。

// http://www.example.org/ 上で実行しているものとする。
var elm = document.createElement('a');
elm.href = '/foo?bar#baz';

// IE 6 / 7 で絶対 URL を設定。
// elm.href = elm.cloneNode(false).href でもよい。
elm.href = elm.getAttribute('href', 4);

// IE (少なくとも 8 ~ 11) で絶対 URL を設定。
elm.href += '';

elm.protocol // => "http:"
elm.hostname // => "www.example.org"
elm.href     // => "http://www.example.org/foo?bar#baz"

しかし IE 6 / 7 の、getAttribute メソッドの第 2 引数に値 4 を渡すことで絶対 URL の値が返るという挙動は、いつ実装されいつ文書化されたのでしょうか?

modern.IE で配布されている IE 6 SP 3 検証環境では確かに動作するのですが、「IE の getAttribute / setAttribute」を書いた時点 (2005 年 10 月) では文書化されていなかった気がします。文書化されていたのなら、「IE 6,7 で相対URL -> 絶対 URL の変換 - #生存戦略 、それは - subtech」にまとめられたような cloneNode メソッドを使った手法を考えることもなかったでしょうから。

なお、「続・IEでのa要素の各属性について - 文殊堂」で言及されていますが、IE 6 / 7 では iframe 要素内の文書で上述の相対 URL 解決手法を用いると、(a 要素を文書木に挿入しない限り) 解決結果のホスト部分が親文書のものになってしまうようです。(ホスト部分が親文書のものになるという現象自体は IE 11 でも発生します。)

呼ばれていないけど、私もコードゴルフしてみました2013年08月09日 03時06分

Code 2013 というイベントで出されたというコードゴルフのお題「JavaScript でデジタル時計」を見ているうちに、自分でもやりたくなったので挑戦してみました。

基本方針

出力が複数行にわたるなら 1 行ずつ処理していくのが素直な手ですが、このお題においてはそれだとひとつの文字に対する処理が細切れになってしまいます。

1 行ずつ処理していく場合のイメージ図

そこで、ある文字の出力処理をまとめてやってしまいましょう。行ごとにではなく列ごとに処理を進めていくのです。

1 列ずつ処理していく場合のイメージ図

20 分

方針が決まれば早速コーディング。以前、渋谷から 10 分のゴルフ場で似たようなお題「banner」に取り組んだときは三十六進数表記を使いましたが、とりあえずは読み書きしやすいよう二進数表記で進めます。

本番の制限時間は 20 分だったそうなので、こちらも 20 分で到達したところはこちら、

t = 0;
setInterval(function () {
    s = ' ■';
    p = [
        '111111000111111',
        '111110000000000',
        '111011010110111',
        '111111010110101',
        '111110010011100',
        '101111010111101',
        '101111010111111',
        '111111000010000',
        '111111010111111',
        '111111010111101',
        '000000101000000',
    ].map(function (b) { return parseInt(b, 2) });
    l = ['', '', '', '', ''];
    d = [
        t / 60 / 10 | 0,
        t / 60 % 10 | 0,
        10,
        t % 60 / 10 | 0,
        t % 60 % 10 | 0,
    ];
    for (i in d) {
        v = p[d[i]];
        for (j = 20; j--;) {
            l[j % 5] += s[v & 1];
            v >>= 1;
        }
    }
    document.body.innerHTML = l.join('<br>');
    t++;
}, 1000);

スペース・改行を取り除いて 458 バイト (UTF-8 でエンコードした場合) と、ちょっと詰めきれていません。二進数表記文字列 + map メソッドの組み合わせを十進整数リテラルに書き換えるだけで 284 バイトになるので、あと一歩ではあったのですが。

改良

このお題ではひとつの文字を表すのに、3 列 5 行、15 個の点を使っています。ひとつの文字に対するパターンが 15 ビットで表現可能ということは、JavaScript の 1 文字 (16 ビット) に収まるわけですね。

ほかにも、変数 sp はそれぞれ 1 箇所でしか使っていないので、変数を使わずリテラルを直接書くといった節約の成果がこれです。

t = 0;
setInterval(function () {
    l = ["", "", "", "", ""];
    for (i in d = [t / 600, t / 60 % 10, 10, t % 60 / 10, t++ % 10])
        /* "\u7E3F\u7C00\u76B7\u7EB5\u7C9C\u5EBD\u5EBF\u7E10\u7EBF\u7EBD\u0140" */
        for (v = "縿簀皷纵粜庽庿縐线纽ŀ".charCodeAt(d[i]), j = 20; j--; v >>= 1)
            l[j % 5] += " ■"[v & 1];
    document.body.innerHTML = l.join("<br>")
}, 1e3)

スペース・改行を取り除いて 230 バイトまで減らせました。さらに、setInterval の第 1 引数を関数ではなく文字列にすることで、220 バイトとなります。

上述の版では文書読み込み完了から 1 秒間が空いて時計の描画が始まります。サンプルと同じく文書読み込み完了と同時に時計が描画される、修正版 (229 バイト) を作りました。v >>= 1v /= 2 にするという変更も入れています。

長い英単語を途中で折り返したいときの CSS の指定方法2013年06月18日 09時32分

ヨーロッパ系の言語では基本的に単語の途中に折り返しを入れません。しかし、幅の狭い領域に長い英単語を記述するときや、笑いを表す「www…www」のように欧文文字を表現に組み込むときなど、欧文文字同士の合間で折り返したいと思うかもしれません。そのような折り返しの制御を CSS で行うにはどうすればよいのでしょうか。

  1. 2013 年 6 月時点の結論
  2. word-wrap: break-word を使うとどうなるのか
  3. word-break: break-all を使うとどうなるのか
  4. word-wrap: break-wordword-break: break-all を両方使うとどうなるのか
  5. なぜ word-break: break-all ではなく word-wrap: break-word を勧めるのか
  6. どこに word-wrap: break-word を指定するのか
  7. position: aboslutedisplay: inline-block との組み合わせ
  8. display: table-cell との組み合わせ
  9. white-space: prewhite-space: nowrap との組み合わせ
  10. word-wrap というプロパティ名
  11. 参考文献

2013 年 6 月時点の結論

  • word-wrap: break-word を使ってください。
  • word-break: break-all使わないでください
  • display: table-celldisplay: inline-block と組み合わせるときは、明示的な最大幅の指定が必要かもしれません。

word-wrap: break-word を使うとどうなるのか

単語 (欧文文字の連続) の途中で折り返さないと行ボックスの幅からあふれてしまうときのみ、その単語の途中で折り返します。単語の途中で折り返しが発生するのは、その単語の幅が行ボックスの幅より大きいときのみです。単語の幅が行ボックスの幅より小さければ、その単語全体が次の行に送られることはあっても、その単語の途中で折り返されることはありません。

[word-wrap: break-word を指定した図] word-wrap: break-word を指定した例。単語 supercalifragilisticexpialidocious の途中で折り返される。

word-break: break-all を使うとどうなるのか

任意の文字間での折り返しが許容され、言語に関わらず一切の禁則処理が無効になります。英単語の途中で行ボックスの幅からあふれそうになれば、あふれる直前で折り返されます。開き括弧の直後や閉じ括弧の直前、句読点の直前でも折り返されます。

[word-break: break-all を指定した図] word-break: break-all を指定した例。単語 such 及び supercalifragilisticexpialidocious の途中で折り返される。

word-wrap: break-wordword-break: break-all を両方使うとどうなるのか

word-break: break-all を使った時点でどの文字の間でも折り返しが起こりうるので、word-wrap: break-word による折り返し発生条件に到達しません。つまり、両方指定するのは word-break: break-all だけの指定と実質変わりません。

なぜ word-break: break-all ではなく word-wrap: break-word を勧めるのか

禁則処理をできるだけ活用し、また単語途中での折り返しを可能な限り避けるためです。ヨーロッパ系の言語で単語途中での折り返しがあると、ひとつの単語がふたつの単語のように見えてしまいます。日本語でも句読点が行頭に来ていれば違和感を持つ人が多いでしょう。

どこに word-wrap: break-word を指定するのか

word-wrap プロパティも word-break プロパティもその値は継承します。言い換えれば、その効果は子孫要素にも及びます。ですから、これらのプロパティは (HTML 文書であれば) body 要素か html 要素に指定しておけば十分でしょう。

position: aboslutedisplay: inline-block との組み合わせ

ときとして長い単語が折り返されず、word-wrap: break-word が効いていないように思えるかもしれません。その単語の祖先要素に position: absolutefloat: leftfloat: rightdisplay: inline-block が指定されていると、このような状況が起こりえます。

これらのプロパティ・値を指定された要素 (で width プロパティの計算値が auto のとき) は、その内容の幅によって自身の幅を決めるのであり、自身の幅を先に決めて内容の幅をそれに合わせるのではありません。このとき、その要素の幅は shrink-to-fit width (内容に合うように縮んだ幅) であるといいます。shrink-to-fit な幅を算出するときに word-wrap プロパティの影響は考慮されないので、単語途中での折り返しは発生しません。

(「考慮されない」といいましたが、内容の幅を決めようというときには行ボックスの幅も定まっていないので、「折り返さないと行ボックスの幅からあふれてしまう」という判断ができません。「word-wrap: break-word の影響を考慮しようにも考慮できない」というのが実際のところでしょう。)

こうした要素に対しては、width プロパティに auto 以外の値を指定することで shrink-to-fit な幅ではなくなります。あるいは、max-width プロパティを指定するのもひとつの手です。いずれにしても行ボックスの最大の幅が定まり、単語の長さがその幅からあふれるときは、その単語の途中で折り返されるようになります。

display: table-cell との組み合わせ

display: table-cell を指定した要素 (セル) も shrink-to-fit な幅の要素と同様、内容の幅によって自身の幅が決まります。しかし、こちらは width プロパティを指定しても単語の途中での折り返されるようになるとは限りません。CSS 2.1 の自動テーブルレイアウトアルゴリズムにおいて、セルの width プロパティの値が最小セル幅として扱われるからです。

(ここで「CSS 2.1 の自動テーブルレイアウトアルゴリズム」と呼んでいるのは CSS 2.1 仕様で定義されているものですが、ブラウザにそのアルゴリズムの利用が強制されているわけではありません。実際に用いられるテーブルレイアウトアルゴリズムはブラウザごとに異なりえます。)

セル内でも word-wrap: break-word による単語途中での折り返しを実現させるためには、セルに max-width プロパティを指定するか (ただし、この方法は IE 8 で機能しないことがあるようです)、セルの子孫要素 (であり、かつ単語の祖先要素でもあるもの) に width プロパティを指定して幅を明示する必要があります。

white-space: prewhite-space: nowrap との組み合わせ

IE 8 以降では、word-wrap: break-wordwhite-space: pre の効果が重なると、なぜか (まるで word-break: break-all を指定したかのように) 任意の文字間で折り返しが発生しうるようです。どこでも折り返されるようになるのは white-space: nowrap と組み合わせたときも同じで、こちらは nowrap といっているのに折り返しが発生することになります。(white-space: pre-wrapwhite-space: pre-line のときは問題ありません。)

white-space: nowrap の使い方として、text-overflow: ellipsis を組み合わせ、行ボックスからはみ出した文字列を省略表示にすることがあります。

h1 {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

しかし、IE 8 以降 (少なくとも IE 10 まで) では、祖先要素に word-wrap: break-word が指定されているとこの省略表示が効かず、内容がひとつの行ボックスに収まりきらなければ複数行で表示されます。white-space プロパティに値 pre または nowrap を指定するときは、word-wrap プロパティの値を初期値に戻しておいたほうがいいでしょう。

h1 {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  word-wrap: normal;
}

word-wrap というプロパティ名

この記事の執筆時点の CSS3 Text 草案では、word-wrap プロパティは overflow-wrap プロパティに改称しています。とはいうものの、互換性のため word-wrap というプロパティ名でも受け付けることになっています。現状のブラウザ実装状況を鑑みるに、word-wrap という名前を使うか、word-wrapoverflow-wrap を併記するかがよいと思います。

参考文献

DBIx::Handler と mysql_enable_utf8 と utf8mb42013年06月10日 21時52分

Perl で MySQL サーバーに接続するとき、DBIx::Handler を使っています。

my $handler = DBIx::Handler->new(
    $dsn, $username, $password,
    {
        RaiseError        => 1,
        RootClass         => 'My::DBI',
        mysql_enable_utf8 => 1,
    },
);

しかし、これだと "𠮷" (U+20BB7、いわゆる「つちよし」) など BMP 外の文字を保存しようとしたとき、テーブルの文字コードが utf8mb4 であっても "????" (疑問符 4 文字) に化けてしまいます。

ググったところ、mysql_enable_utf8 => 1 を指定した時点で DBD::mysql が接続時の文字コードを utf8 にしてしまうのが問題だそうです。(cf. おそらくはそれさえも平凡な日々: DBD::mysqlでmysql_enable_utf8しつつutf8mb4使いたいとき)

接続時に文字コードを utf8mb4 に指定しなおせばいいとのことですが、DBIx::Handler ではどうするのだろうとソースコードを眺めていたところ、new メソッドの第 5 引数の存在を知りました。

my $handler = DBIx::Handler->new(
    $dsn, $username, $password,
    {
        RaiseError        => 1,
        RootClass         => 'My::DBI',
        mysql_enable_utf8 => 1,
    },
    {
        on_connect_do     => ['SET NAMES utf8mb4'],
    },
);

これで BMP 外の文字も文字化けせず保存・取得できるようになったのですが、この第 5 引数は (DBIx::Handler 0.07 時点では) 文書化されていないので、使っていいものか心配です。

Callbacks を使っても期待通り動作するようなので、そちらのほうがよかったりするのでしょうか。

my $handler = DBIx::Handler->new(
    $dsn, $username, $password,
    {
        RaiseError        => 1,
        RootClass         => 'My::DBI',
        mysql_enable_utf8 => 1,
        Callbacks         => {
            connected => sub {
                $_[0]->do('SET NAMES utf8mb4');
                return;
            },
        },
    },
);

(SET NAMES をつかってはいけないという話もありますが、追いきれていません。)

追悼 E4X (仮) 発表資料2013年05月27日 21時49分

Firefox 21 で E4X のサポートが削除されたのを受け、「追悼 E4X (仮)」というイベントが開催されました。東京での開催だったのですが、私自身仕様の邦訳を手がけるなど E4X には並々ならぬ想いがあり、京都から駆けつけた次第です。

来たからにはと私も「E4X と autovivification」という題で LT をしてきました。Perl でいうところの autovivification という機能が E4X にも備わっているという話です。ほかに「私と E4X」という発表 (むしろ自分語り) もしたのですが、こちらはその場限りのオフレコです。

追悼というだけあって皆さん E4X に対する熱い思いを語っていましたが、特に感心したのが Vimperator の対応の話です。それまで E4X を使っていた部分を、ECMAScript 6 での採用が検討されているテンプレートリテラルに置き換えたものの、それ自体現在の SpiderMonkey では実装されていません。そこで、chrome://liberator/....js と JavaScript ファイルを読み込んでいたのを、liberator://....js と独自プロトコルを介した読み込みに変更し、そのプロトコルハンドラの中でソースコード変換を行っているとのことでした。

私も製品のコード中で E4X を使っており、結果として後進に負の遺産を残すこととなってしまったのですが、一方で E4X があったから今の私があるというのもひとつの真実であり、E4X 仕様及びそれを実装した Mozilla に深く感謝します。