漢字にマッチする JavaScript の正規表現パターン ― 2015年12月31日 23時03分
たまに漢字にマッチする正規表現パターンを書きたいときがあります。Perl の正規表現だと Unicode のスクリプト名を使って \p{Han}
で漢字にマッチさせられるのですが、JavaScript ではそうはいきません。JavaScript の正規表現には以下のふたつの問題があります。
- Unicode スクリプト名の指定 (
\p{...}
) に対応していない。 - そもそも Unicode の符号位置に対してマッチさせられない (UTF-16 における符号単位に対するマッチになる)。
- BMP 外の文字にマッチさせたいときは、サロゲートペアの符号単位を記述する必要がある。
- これに関しては ECMAScript 2015 で導入された
/u
フラグで解決する見込み。
とはいえ、解決不能な問題というわけでもないので、Perl の \p{Han}
を JavaScript に移植してみましょう。\p{Han}
の範囲は以下のコマンドで参照できます。
perl -MUnicode::UCD -MData::Dumper -E 'say Dumper Unicode::UCD::charscript("Han");'
Perl 5.20.2 (Unicode 6.3.0) では以下の範囲が含まれるとわかりました。
U+2E80
-U+2E99
U+2E9B
-U+2EF3
U+2F00
-U+2FD5
U+3005
-U+3005
U+3007
-U+3007
U+3021
-U+3029
U+3038
-U+303B
U+3400
-U+4DB5
U+4E00
-U+9FCC
U+F900
-U+FA6D
U+FA70
-U+FAD9
U+20000
-U+2A6D6
(\uD840\uDC00
-\uD869\uDED6
)U+2A700
-U+2B734
(\uD869\uDF00
-\uD86D\uDF34
)U+2B740
-U+2B81D
(\uD86D\uDF40
-\uD86E\uDC1D
)U+2F800
-U+2FA1D
(\uD87E\uDC00
-\uD87E\uDE1D
)
サロゲートペアが [\uD800-\uDBFF][\uDC00-\uDFFF]
であることを踏まえると、BMP 外の範囲はそれぞれ以下の部分的な範囲に展開できます。
\uD840\uDC00
-\uD869\uDED6
[\uD840-\uD868][\uDC00-\uDFFF]
\uD869[\uDC00-\uDED6]
\uD869\uDF00
-\uD86D\uDF34
\uD869[\uDF00-\uDFFF]
[\uD86A-\uD86C][\uDC00-\uDFFF]
\uD86D[\uDC00-\uDF34]
\uD86D\uDF40
-\uD86E\uDC1D
\uD86D[\uDF40-\uDFFF]
\uD86E[\uDC00-\uDC1D]
\uD87E\uDC00
-\uD87E\uDE1D
\uD87E[\uDC00-\uDE1D]
これらを整理して組み合わせると、最終的に以下のパターンが得られます。
/(?:[\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u3005\u3007\u3021-\u3029\u3038-\u303B\u3400-\u4DB5\u4E00-\u9FCC\uF900-\uFA6D\uFA70-\uFAD9]|[\uD840-\uD868][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|[\uD86A-\uD86C][\uDC00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]|\uD87E[\uDC00-\uDE1D])/
というわけで「漢字 1 文字にマッチする JavaScript の正規表現パターン」でした。よいお年を。
IE で a 要素を使って相対 URL からスキームを得る ― 2014年11月09日 20時22分
いくつかのブラウザではURLがhttpの相対リンクであってもelm.protocolが「:」や空文字になったりする
この「いくつかのブラウザ」とは 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 行ずつ処理していくのが素直な手ですが、このお題においてはそれだとひとつの文字に対する処理が細切れになってしまいます。
そこで、ある文字の出力処理をまとめてやってしまいましょう。行ごとにではなく列ごとに処理を進めていくのです。
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 ビット) に収まるわけですね。
ほかにも、変数 s
、p
はそれぞれ 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 >>= 1
を v /= 2
にするという変更も入れています。
長い英単語を途中で折り返したいときの CSS の指定方法 ― 2013年06月18日 09時32分
ヨーロッパ系の言語では基本的に単語の途中に折り返しを入れません。しかし、幅の狭い領域に長い英単語を記述するときや、笑いを表す「www…www」のように欧文文字を表現に組み込むときなど、欧文文字同士の合間で折り返したいと思うかもしれません。そのような折り返しの制御を CSS で行うにはどうすればよいのでしょうか。
- 2013 年 6 月時点の結論
word-wrap: break-word
を使うとどうなるのかword-break: break-all
を使うとどうなるのかword-wrap: break-word
とword-break: break-all
を両方使うとどうなるのか- なぜ
word-break: break-all
ではなくword-wrap: break-word
を勧めるのか - どこに
word-wrap: break-word
を指定するのか position: aboslute
やdisplay: inline-block
との組み合わせdisplay: table-cell
との組み合わせdisplay: flex
との組み合わせwhite-space: pre
やwhite-space: nowrap
との組み合わせword-wrap
というプロパティ名- 参考文献
2013 年 6 月時点の結論
word-wrap: break-word
を使ってください。word-break: break-all
は使わないでください。display: table-cell
やdisplay: inline-block
と組み合わせるときは、明示的な最大幅の指定が必要かもしれません。
word-wrap: break-word
を使うとどうなるのか
単語 (欧文文字の連続) の途中で折り返さないと行ボックスの幅からあふれてしまうときのみ、その単語の途中で折り返します。単語の途中で折り返しが発生するのは、その単語の幅が行ボックスの幅より大きいときのみです。単語の幅が行ボックスの幅より小さければ、その単語全体が次の行に送られることはあっても、その単語の途中で折り返されることはありません。
word-wrap: break-word
を指定した例。単語 supercalifragilisticexpialidocious の途中で折り返される。
word-break: break-all
を使うとどうなるのか
任意の文字間での折り返しが許容され、言語に関わらず一切の禁則処理が無効になります。英単語の途中で行ボックスの幅からあふれそうになれば、あふれる直前で折り返されます。開き括弧の直後や閉じ括弧の直前、句読点の直前でも折り返されます。
word-break: break-all
を指定した例。単語 such 及び supercalifragilisticexpialidocious の途中で折り返される。
word-wrap: break-word
と word-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: aboslute
や display: inline-block
との組み合わせ
ときとして長い単語が折り返されず、word-wrap: break-word
が効いていないように思えるかもしれません。その単語の祖先要素に position: absolute
や float: left
、float: right
、display: 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
プロパティを指定して幅を明示する必要があります。
display: flex
との組み合わせ
flexbox レイアウトとの組み合わせでも折り返しが効かないように思えることがあるそうです。
white-space: pre
や white-space: nowrap
との組み合わせ
IE 8 以降では、word-wrap: break-word
と white-space: pre
の効果が重なると、なぜか (まるで word-break: break-all
を指定したかのように) 任意の文字間で折り返しが発生しうるようです。どこでも折り返されるようになるのは white-space: nowrap
と組み合わせたときも同じで、こちらは nowrap
といっているのに折り返しが発生することになります。(white-space: pre-wrap
、white-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-wrap
と overflow-wrap
を併記するかがよいと思います。
参考文献
DBIx::Handler と mysql_enable_utf8 と utf8mb4 ― 2013年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
をつかってはいけないという話もありますが、追いきれていません。)
最近のコメント