長時間のフライトで役立ったグッズ
Scala Daysに参加するため、昨日からコペンハーゲンに来ています。
日本からのフライトは10時間ほどかかるのですが、昨年のサンフランシスコへのフライト(13時間!)では完全に腰を撃破されてしまったため、今回は快適に過ごせるように便利グッズをいくつか持ち込んでみました。
Rock Dan(収納ケース付き)フットレスト
座席前のテーブルに引っ掛け、足を乗せて使うものです。見た目も構造も簡素なのですが大変効果がありました。足を投げ出せるだけでだいぶ違います。
もともとは¥8,000ほどするものらしいですが、今は83%オフ(!)で¥1,000ちょっとなのでエイヤーと買ってしまいました。このくらいの価格で買えるなら2~3時間のフライトでも持っていて損はないと思います。
HOMECUBE 改良版 エアーピロー
膨らませて使う大型の枕です。大型と言ってもそこまで大量に息を吹き込む必要はありませんでした。
折りたたんだ状態では小さい水筒ほどのサイズなので、カバンにぽんと入れて持っていきました。(使った後自分でたたむのはなかなか大変でしたが…)
飛行機の座席を倒しづらいシーンはままあると思うのですが(いかついお兄さんが後ろに座ってるとか)、そういう際にこういう枕があると非常に便利だと思います。
中がちょっとした空洞になっているので、中でスマホや携帯ゲーム機を使ったりもできます。以前にちょっと話題になった1人ダンボールシアターと同じ要領ですね。
腰も無事フライトに耐えきったので、今日からのScala Daysを楽しみたいと思います!
「JSONにコメント書きたい!」って思ったあなたにはHjsonがおすすめ。
JSONにコメントを残したいなあと思うことは多々あるかと思います。 特に設定ファイルの場合、「なぜこの値にしているのか」「取りうる値の範囲」などの情報を残しておけるとありがたそうですね。
先日、Hjsonなるものを発見しました。コメントが残せる以外にも、末尾のカンマやクオーテーションが不要だったりと、なかなか快適そうです。
Javaでの実装があるので、これをScalaから利用してみます。
hjson-java
出力
Name: Shunsuke Tadokoro Colors: ["red","green","blue"] Greet: Hello, HJSON! Regex: ^\d{2,4}\.\w+?-\d+ HTML: <h1>HTML Element</h1> 若さ 若さってなんだ ふりむかないことさ 愛ってなんだ ためらわないことさ
ここまで簡潔に書きたいならそもそもYAMLでいいのではという気もしますが、 通常のJSONもHjsonとしてパースできるという後方互換性(?)があるので、 JSONを扱う既存のコードにすんなりと組み込めるという点ではこちらに分がありそうです。
ScalaでウェブページのCharsetを取得する
サイトのエンコードは、
- HTML, head内のmeta charset
- レスポンスヘッダのContent-Type内
の2箇所に記述してあるケースが多いのですが、前者はサイト作成者の記述ミスが多々あるため信用できず、後者もまれにエンコード名として異常な文字列が入っていることがあります。
(以前、charset=%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89
という胸騒ぎがする指定を発見し、いざデコードしてみるとcharset=文字コード
が出てくるというハートウォーミングな出来事がありました。マジかよ。)
そのため、エンコードを判定する機能が必要になります。
今回、判定にはMozilla製エンコード検出ライブラリuniversalchardetのJava実装版であるjuniversalchardetを利用しました。
juniversalchardet - Google Code Archive
エンコードを判別したいバイト列をUniversalDetectorクラスのインスタンスにハンドリングさせエンコード名を取得、と利用するようです。
Javaから利用するサンプルコードが公式にあります。
ただなんでもかんでもこいつに判定させるのはパフォーマンス的によろしくなさそうなので、 あくまでも「Content-Typeのcharsetが存在しない、もしくはエンコードとして扱えない文字列が指定されている場合」のみ判定をを行うのがよさそうです。
import java.nio.charset.Charset import scala.util.{Failure, Success, Try} import org.mozilla.universalchardet.UniversalDetector /** * Content-Typeヘッダからcharsetを取得する。 * ただし、Content-Typeで指定されたcharsetがエンコーディング名として不正な場合は、渡されたバイト列からcharsetを検出する。 * エンコーディング名として正しいcharsetがContent-Typeから取得できず、渡されたバイト列からも検出できない場合はNoneを返す。 */ protected def getCharset(contentType: String, bytes: Array[Byte]): Option[String] = { def _detect(bytes: Array[Byte]): Option[String] = { val ud = new UniversalDetector(null) ud.handleData(bytes, 0, 1024) // パフォーマンスを考慮し、先頭1024バイトのみで判定する ud.dataEnd() Option(ud.getDetectedCharset) } """.*charset=(.+)""".r.findAllIn(contentType).matchData .map(_.group(1)).toList.headOption.flatMap { charsetName => Try { Charset.forName(charsetName) } match { case Success(_) => Some(charsetName) case Failure(_) => _detect(bytes) } } } val res = ... // レスポンス val contentType: String = res.getContentType val bodyAsBytes: Array[Byte] = res.getResponseBodyAsBytes val charset: Option[String] = getCharset(contentType, bodyAsBytes)
まだ改良の余地はありますが、これでひとまず判定ができるようになります。