« 2007年8月 | メイン | 2007年10月 »

2007年9月28日

hasLayoutとは何か
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

yamaokaです。

CSSに携わっている方なら、Holly hackを使ったことがあるかもしれません。

/* Hides from IE5-Mac \*/
* html .foo { height: 1%; }
/* */

Internet Explorer(以下IE)で、レイアウトに問題のある要素に 上記のようなスタイルを指定をすると、 あら不思議、まともな表示がなされるというものです (上記のままではIE7に対応していませんが…)。

さて、どうしてレイアウトが意図したとおりに行われるようになるのでしょうか。

IEのhasLayoutプロパティ

IEでは、全ての要素が 「hasLayout」という読み取り専用のプロパティを持っています。 これはそれぞれの要素がレイアウト情報(=要素の幅・高さなどに関する属性情報)を 保持しているかどうかを示す値で、 デフォルトの状態では「hasLayout = false」です。 「hasLayout = false」の場合、幅や高さが他の要素のレイアウトに影響されて伸び縮みし、 表示が他の要素と重なってしまう場合があります(レイアウト崩れの原因)。

hasLayoutの値を調べたい場合、IE Developer Toolbar で要素を選択すれば確認することができます(ちなみに、「-1」というのが「hasLayout = true」の状態で、 「hasLayout = false」の場合、hasLayoutプロパティ自体表示されません)。

hasLayout
hasLayout posted by (C)フォト蔵

hasLayoutの値を切り替える

「hasLayout = false」の状態でレイアウトが崩れる場合、 「hasLayout = true」に値を切り替えると問題が解決する場合が多いです。 切り替える方法ですが、CSSの特定のプロパティに下記のような値を指定することで可能なようです (MSDNのドキュメントによる)。

プロパティ
displayinline-block
heightany value
floatleft or right
positionabsolute
widthany value
writing-modetb-rl
zoomany value

hasLayoutとの付き合い方

冒頭で掲げたHolly hackは、 heightをスタイルで指定することで「hasLayout = true」に切り替え、 レイアウト情報を要素に持たせるための工夫だったわけです。 IEでレイアウト崩れがどうしても直らない場合、 hasLayoutの値を確認してみるといいかもしれませんね。

最後に、記事を書く上で参考にしたサイトを挙げておきます。

【追記】参考にしたサイトとして「IEでのCSSのバグを回避するhasLayout | コリス」を追記しました。記事を書く上で非常に参考にさせていただきました。記載漏れ申し訳ありませんでした。

携帯サイトとクローラ
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

harukiです。

ケータイユーザも検索エンジンから来るユーザも増えています。
そのため、携帯サイトも検索エンジンのクローラへ対応する必要があります。

・Mobile Link Discoveryの記述を追加する

PC用のページのheadタグ内に

<link rel="alternate" media="handheld" href="(ケータイURL)" />
の記述を追加するだけです。

Mobile Link Discoveryに対応しているサイトでは、ケータイからのアクセス時に直接アクセスしてもらえるようになります。

Mobile Link Discovery 仕様
http://www.sixapart.jp/docs/tech/mobile_link_discovery_ja.html

 

検索エンジンでは、Googleモバイルのgoogle mobile proxy
http://www.google.co.jp/gwt/n

Yahoo!モバイルでの検索→PCサイトの結果のところにケータイサイトのURLが追加されるようです。

(他の検索エンジンでは対応してたり、してなかったりします)

また、はてなMobileGatewayも対応しています。
http://mgw.hatena.ne.jp/help

 

PC用のページに1行追加するだけですので簡単です。

…ということで、昨日、ビデオポップsugu.CCにも追加しておきました。

・ケータイ検索エンジン用クローラ対策

先ほどのMobile Link Discoveryは主にPC用の検索エンジンのクローラ等への対応になります。
対応しておいて損はないという程度だと思います。

やはり、ケータイサイトであるならば、ケータイ用の検索エンジンのクローラに対応してあげるほうが効果は高いです。

サイトでSessionや端末IDを使用する場合は、各キャリアのIPアドレス帯域による制限を行っていると思います。
しかし、それではクローラのアクセスも弾かれてしまいますので、クローラにもケータイ向けのページを表示してあげる必要があります。

対応する際には、以下の順序で対応していくのがいいと思います。

  • プログラムでキャリアのIP帯によるアクセスなのかを判断できるようにする
    .htaccessなどでうまくやる方法があれば教えて下さい
  • クローラのIPもしくはUserAgentのアクセスを許可する
    クローラの一覧はぜひ共有したいです
  • Sessionの利用・端末IDの取得はキャリアのIP帯からしか許可しない
    キャリアのIP帯以外からの場合はSessionは「使わない・破棄する」
このように対応すると、ログイン後に見れるページ以外は検索エンジンに登録してもらえるようになります。

・検索ワードの把握

Referrerを送ってくれるauとSoftBankでは検索ワードが取得することができます。
webalizerの場合は、webalizer.confにこんな感じで追加するとSearch Stringの取得ができます。

SearchEngine    google.co.jp    q=
SearchEngine    yahoo.co.jp     p=
SearchEngine    ezweb.ne.jp     query=
SearchEngine    livedoor.com    q=

まだまだ、勉強不足な分野ですので、ツッコミや追加の情報などがあれば是非、教えていただければと思います。

2007年9月27日

dRuby/Rinda/Ringでジュークボックス
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

komagataです。

最近社内ではiTunes専用のPCを用意して社内BGMを流しています。

「コマンドラインから曲のリクエストを出せないかな」と思って、勉強がてらRubyを使ってジュークボックスサーバ、ジュークボックスクライアントを作ってみました。

必要な機能は、

  • 簡単に曲のリクエストを出せるクライアント
  • 複数のクライアントから曲のリクエストを受付け、保持し、順次再生するサーバ
  • クライアントが自動的にサーバを見つけるためのネームサーバ

などで、考えてみるとかなり面倒臭そうです。iTunesはDAAP(Digital Audio Access Protocol)というプロトコルで音楽をやり取りしたり、mDNS(Multicast Domain Name Service)というネームサービスを使ってサーバを見つけたりしているそうです。

今回は、dRubyを使って曲をやり取りし、RindaのTupleSpaceを使って曲の保持、クライアントとの待ち合わせをし、Ringをつかってクライアントがサーバを見つける仕組みを作りました。

スクリプトはそれぞれ下記のようになっています。

  • jukebox_client.rb - 曲のリクエストを出すクライアント
  • jukebox_server.rb - 曲のリクエストを保持し、順次再生するサーバ
  • jukebox_ring_server.rb - クライアントが自動的にサーバを見つけるためのネームサーバ

利用方法

サーバ側では音楽再生のためにコマンドラインでmp3などが再生できるmpg123が必要です。(下記はUbuntu 7.04の場合)また、サーバ、クライアント共にRubyが必要です。(Ruby 1.8.6で動作確認をしています)

sudo apt-get install mpg123 mpg123-alsa

まず、サーバ側で下記でネームサーバを立ち上げます。

ruby jukebox_ring_server.rb

同じくサーバ側でジュークボックスサーバを立ち上げます。

ruby jukebox_server.rb

あとはクライアント側で再生したいMP3ファイルをしていしてクライアントを実行します。

ruby jukebox_client.rb Heart_Of_The_Sunrise.mp3

クライアントは同じLAN内にブロードキャストしてネームサーバを見つけ、そこに登録してあるジュークボックスサービスに曲のリクエストを追加します。サーバは曲がくるまで待ちうけ、リクエストを順次再生します。

ソースを見てもらうとわかりますが、Ringのサンプルほぼそのままです。

jukebox_server.rb:

#!/usr/bin/env ruby
require 'rinda/ring'
require 'rinda/tuplespace'
require 'tmpdir'
 
class JukeboxServer
  def initialize
    @play_list = Rinda::TupleSpace.new
  end
 
  def play
    puts "waiting..."
    name, music_file = @play_list.take([:music, nil])
    puts "playing..."
    system "mpg123 #{music_file}"
    File.delete "#{Dir.tmpdir}/#{music_file}"
  end
 
  def request(file_name, music_data)
    open("#{Dir.tmpdir}/#{file_name}", "w") {|f| f.write music_data }
    @play_list.write([:music, "#{Dir.tmpdir}/#{file_name}"])
  end
end
 
jukebox_server = JukeboxServer.new
DRb.start_service(nil, jukebox_server)
 
provider = Rinda::RingProvider.new(:jukebox, DRbObject.new(jukebox_server), 'Jukebox Server')
provider.provide
 
loop { jukebox_server.play }
 
DRb.thread.join

クライアントはDRbオブジェクト経由でサーバ側のtmpディレクトリに音楽ファイルを保存し、そのパスをTubpleSpaceに保持して、サーバーが順次再生します。

こんな短いコードで分散オブジェクトやネームサービスが実現できることに驚きました。Ringに関しては複数のIPがある場合にうまくいかないところがあってまだ勉強中です。

コマンドラインから音楽のリクエストが出せて嬉しいかどうかはさておき、他に色々と面白い使いかたができそうです。

ソース: jukebox.tar.bz2

2007年9月25日

携帯キャリアで画像を転送させない方法
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

Keitaです。

携帯画像で待ち受け画像などで、ダウンロードはできるけどメールに添付できないタイプの画像があるとおもいます。
ここらへんどうやるか、気になって調べてみると、

Docomoと、AUは画像のコメント部分に、それぞれ、特定の文字列を入れれればいいようで、 Docomoの場合「copy="NO"」KDDIの場合「kddi_copyright=on」を追加することでで、携帯でダウンロードできるが、メールなどで転送できない画像が作れました。

具体的に、MagickWand For PHPを使う場合には以下のような感じでヘッダを埋め込みます。
<?php
    $image =  NewMagickWand();
    MagickReadImage($image, 'Keita_s.jpg');
    MagickCommentImage($image, 'kddi_copyright=on,copy="NO"');
    MagickWriteImage($image, 'Keita_s_Copy.jpg');

SoftBankに関しては、次のようなヘッダを出力追加してやれば再配布を禁止することができるようです。
「x-jphone-copyright: no-transfer」
技術資料 HTTP編のPDFの「3.15.38」の項目に、詳細が乗っています。


動的に出力したい場合には、こんな感じのコードを書いてやるとよさそうです。
<?php
    $image =  NewMagickWand();
    MagickReadImage($image, 'Keita_s.jpg');
    MagickCommentImage($image, 'kddi_copyright=on,copy="NO"');
    header("Content-type: " . MagickGetImageMimeType($image));
    header('x-jphone-copyright: no-transfer');
    MagickEchoImageBlob($image);
なお、3キャリアを1機種ずつ確認し問題のないことを確認しましたが、責任を取れるほど詳しくは検証していませんので、厳密にやりたい場合は、必ず検証ののちご利用ください。

あと当然ですが、上記の3キャリア以外からアクセスできると、画像は取り放題ですのでご注意ください。

参考にさせていただいたサイト

以上、ご参考になれば幸いです。

Rubyでネットワークサーバを書く
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

尾藤正人(a.k.a BTO)です

先日公開したブラウザだけでネットワーク対戦ゲームができるサイト「プラッシュ」では、 フラッシュとネットワーク通信を行う専用のXMLSocketサーバを開発しました。 このXMLSocketサーバはrubyで書かれています。 LLでデーモンを書く需要が、それほどあるとは思えませんが、デーモンを書く際に気をつけた点、工夫した点をまとめてみたいと思います。

なぜrubyを選んだのか

rubyを選んだのには理由は2つあります。

  • Railsを採用した
  • LLで早く開発をしたかった

僕も昨今のRailsブームにのって個人的にRailsを使い始めていました。 プラッシュは完全に新規プロジェクトで環境を選択する事ができたので、迷わずRailsを選択しました。

では、なぜCのようなコンパイル言語で書かなかったのか。 速く動くものを開発するよりも、早く開発をしたかったからです。 Webサービスにとって開発スピードは最も重要な項目の1つです。 最初から速いものを作る必要はありません。早く市場に出さないとうまくいくかどうかもわかりません。 速くするのは後からでも全然構わないのです。

デーモンになる

デーモンになるために必要なのは次の4つです。

  • fork -> setsid -> fork
  • ルートディレクトリに移動する
  • umask をクリアする
  • 標準入出力、標準エラー出力を /dev/null に向ける

僕は次のようなメソッドを定義して実行してます。

def daemonize
  exit! 0 if fork
  Process::setsid
  exit! 0 if fork
  Dir::chdir('/')
  File::umask(0)
  STDIN.reopen  '/dev/null', 'r' 
  STDOUT.reopen '/dev/null', 'w' 
  STDERR.reopen '/dev/null', 'w' 
end

デーモンについては以前勉強会やった内容を公開していますので、よかったらそちらをご覧下さい。 UNIXデーモンを作ろう

全てのエラーを補足する(堅牢性)

デーモンはバックグランドで24時間動き続けないといけません。 デーモンが落ちてしまうと全くサービスが継続できなくなってしまいますから、多少のエラーが発生しても大丈夫なように作らないといけません。 幸いな事にrubyの場合は、ruby自体が落ちてしまわない限り、あらゆるエラーを補足することができますので、上位のところで全てのエラーを補足するようにするだけで問題ありません。

メモリリークに気をつける

Webアプリのプログラムのように短時間で処理が終了してしまうものは、メモリリークを気にする必要などほとんどありませんが、 デーモンのように24時間動き続けるようなシステムでメモリリークがあると、 最初は調子よく動いていても長い間実行するとメモリをたくさん消費して大変な事になってしまいます。 といってもrubyの場合はruby側でメモリ管理をしてくれるのでメモリリークする心配はあまりありません。

スコープ内にあるローカル変数はスコープから抜けたら勝手にrubyのGCが開放してくれるので気にする必要はありません。 気をつけないといけないのは永続的に使用しているオブジェクトになります。 永続的に使用しているオブジェクトに必要ないものが出てきたら忘れずに、Array#delete、Hash#deleteを呼ぶか、nilを代入するなどして、 rubyのGCが開放できるような状態にしてやる必要があります。

LLのGCのアルゴリズムに参照カウンタを採用しているものがありますが(php, perl, python)、GCのアルゴリズムが参照カウンタの場合、 循環参照のオブジェクトの開放ができなくてメモリリークになってしまう問題があります。 rubyのGCのアルゴリズムはmark-sweepになってるそうなので、この点は安心できます。 pythonは参照カウンタですが、循環参照のオブジェクトを開放する機構があるそうです。

IOを多重化する

ネットワークサーバは(inetdのようなものを使わない限り)複数コネクションを扱えないといけません。 通常ネットワークサーバが複数プロセスを扱う時は1コネクションに対して、 1プロセス or 1スレッドを生成するマルチプロセス/スレッドモデルで処理をするのが一般的です。

rubyで実装するので、プロセスモデルでコネクションをさばくには処理速度やリソース消費の観点から懸念がありました。 実際に試してないのでもしかしたら余計な心配だったのかもしれませんが。 スレッドモデルも考えたのですが、 rubyのスレッドはネイティブスレッドではなくruby自身が持っているもので、 複数コネクションをさばくにはお話にならないくらいコンテキストスイッチの速度が遅くて使い物になりませんでした。

そこでIO#selectを使ってIOを多重化することにしました。 それぞれ単純な役割を持った4つのスレッド「コネクションをacceptする」「ソケットから入力を読み取りキューに登録する」「接続が切れたコネクションを開放する」「キューから命令を取り出して実行する」で処理を行うようにしました。 次のようなプログラムで処理しています。

def main
  server = TCPServer.new(@port)

  Thread.fork {
    while true
      begin
        add_connection(server.accept)
      rescue Exception => error
        logger.fatal error
      end
    end
  }

  Thread.fork {
    while true
      begin
        read_instructions
      rescue Exception => error
        logger.fatal error
      end
    end
  }

  Thread.fork {
    while true
      begin
        manage_connection
      rescue Exception => error
        logger.fatal error
      end
    end
  }

  while true
    begin
      dispatch
    rescue Interrupt
      break
    rescue Exception => error
      logger.fatal error
    end
  end
rescue Exception => error
  logger.fatal error
  exit(1)
end

このように生成されるスレッドを限定する事でコンテキストスイッチの遅さは問題なくなります。 IOを多重化することでコネクションが増えても、1ソケット分のオブジェクトが増えるだけになり、 コネクションが増えてもそんなに負荷が上がらないような構造になっていると思います。

とはいえ、この方式にもいくつか問題点はあります。 1つは命令実行スレッドが命令をシーケンシャルに実行するので、1つ重い処理があると他の命令が実行されなくなってしまうので、 全体の処理に影響を与えてしまうことです。 これに関しては命令実行スレッドを増やしたり、命令に実行優先順位をつけるなどして対処することができると考えています。

もう一つはマルチコアのCPUで1つのコアしか活用できない点です。 rubyのスレッドはネイティブスレッドではないので、OSから見るとただの1プロセスにしか見えません。 なので、せっかくマルチコアのCPUを搭載していても1つのコアしか使用する事ができません。 これに関しては複数プロセスで動作できるようにしたり、少し作り込みが必要になってきます。

まとめ

rubyでネットワークサーバを書く時に気をつけた点についてまとめてみました。 他にも「こうした方がいいよ」とか「こういうのがあるよ」とかありましたら、いろいろ教えていただければと思います。

2007年9月21日

ユーザビリティテスト初心者のための備忘録 - 準備の話
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちわ、Sashaです。9月も終わりに近づいているというのにまだまだ暑いですね。

私はユーザビリティテストの専門家ではありませんし、ユーザビリティテストを実施していても、不慣れから来る反省点が毎回のように残ります。今日は、そんなユーザビリティテストにまだ不慣れな方のために、私がこれまでに書きとめておいた反省点やコツをみなさんと共有したいと思います。スムースなテストを開始して、被験者の方にもリラックスして頂くことが出来るように気をつけるべき細かい点ですので、テスト方法とか、被験者の選定方法など、ユーザビリティテストのコアの部分には触れていませんが、ご了承ください。

1. 会場取り

ビデオカメラやモニタを設定するため、被験者が来られる15分ほど前から会場となる部屋を押さえておきましょう。11時に被験者の方がいらっしゃるのに、 11時からしか部屋を押さえていないと、準備する時間がないため非効率的ですし、こちらの気持ちがバタバタしていると被験者の方にリラックスしていただくことが出来ません。

2. テストの目的や方法を説明する簡単な文書を用意する

ユーザビリティテストといっても、一般の方にはあまりぴんと来ません。自分がすることが、どういう目的に使われて何の役に立つのか、業界外の方にも良く伝わるような文書を用意しておきましょう。ポイントは、「テスト」という言葉は使わないこと。「テスト」というとなんとなく、高得点を取る事が求められているかのような印象を与えかねないからです。私は、「よりよいウェブサイトを作るための調査」とか言ってますが、「調査」も硬いかな、と思っていて、いい言葉があれば差し替えたい感じです。

一般的にテストの説明を(時には口頭で補足しつつ)文書でお渡しするタイミングは2回あるかと思います。1回は事前に被験者の方にお送りしておく場合。2回目は実際にテストを始めるときに、導入として軽くテストの主旨を説明する場合。前者の場合は、被験者の人が事前に対象となるサイト等について下調べをしてきてしまうことのないように該当サイト自体を言及することを避けたり、後者の場合は、緊張したり構えてしまうことのないように、「いつもの生活の中でインターネットをお使いになられている様子を参考にさせていただきたい」旨を強調するといいと思います。

3. 個人情報・肖像権の取り扱いについての覚書を用意する

ユーザビリティテストでは、ビデオカメラやスクリーンキャプチャで被験者の会話や表情、デスクトップの動きを録画して、あとで検証するために記録として残しておくと非常に有効です。ただ、そうするにあたり個人情報や肖像権をテストを実施する側の私たちがどのように扱うか、ということを明確にしておく必要があります。ビデオに撮られる事を喜ぶ方はそれほどいないものですが、あえて撮影をさせていただくことの意義をきちんと説明して、動画データは一切外には漏れないことを強調しておけば、大概の方にはご理解いただけるかと思います。

私の場合、2と3を一緒にして、下のような文書を作っています。

  • テストの目的と方法(撮影します、と言及)
  • 動画や個人情報は○○(責任者の名前)の管理の下、○○株式会社の○○チームのメンバーのみが上記の目的のみに閲覧させていただく旨を説明
  • 責任者の名前
  • 紙の下半分に、フォームを作り、お客様の氏名、住所、メールアドレス等を記入してもらう→データ管理用

それで、口頭で補足説明をしたうえで、フォームにご記入いただいたものを頂戴し、もう一枚を、覚書として被験者の方にお渡ししてます。

4. お礼状のテンプレートを用意する

実際にそのサイトを利用したことが一度もなくてもそのサイトで提供している商品やサービスに関心がある人、つまり、「今後サイトに来ていただきたいお客様」が、ユーザビリティテストの対象者としてふさわしいということはブログや本でもよく説明されています。このような対象者を慎重に選定した場合、テストにご協力いただく方は、もちろん大切な大切なお客様です。

ご協力いただいたらすぐに、メールやお手紙で、長時間の拘束をお詫びしつつもご協力いただいたお礼ができるよう、テンプレートを準備しておくと便利です。だれでも、人の役に立つってうれしいものです。ご協力いただいたことがあなたのプロジェクトにどれほど役に立つことになるか、強調しましょう。

テストで実際にお客様にサイトでお買い物をしていただいたりする場合、お礼状を商品に付けてお送りできるように手配することもあります。

5. 謝礼の準備

ご協力いただいた方にどのような謝礼を差し上げるかは、ケース・バイ・ケースかと思います。謝礼のお金をテストの際にお渡しする場合は、お金だけではなく封筒などの準備も抜かりなく。もしも、自社の通販サイトがテストの対象だったりする場合は、被験者の方に実際にサイトで買い物をしていただいてそれを謝礼とするなども可能かと思います。その場合、配送センターなど実際に商品を発送する事になる部署の方とも、被験者の方を見分ける事ができるよう必要な情報共有をしておいて、請求書を同封しないなどの手順を確認しておきましょう。

6. 機材・備品の準備

いよいよテスト当日になって、上記の準備をしっかりしてあっても、まだまだ準備しなければならないことはたくさんあります。
例えば、
  • カメラの操作方法は十分に練習して即座に対応できるようにしておきましょう
  • カメラや、ノートパソコンなどのバッテリーが充電式である場合、フル充電にしておきましょう
  • 会場のカメラやパソコンの配線状態を前もって調べておきましょう。延長コードや蛸足配線が必要な場合があります
  • カメラのマイクできちんと音声も録音されるかを事前にテストしておいて、もし十分に音声を拾えないようならマイクを用意しましょう
  • 記録メディアに十分な空き容量があるか確認しておきましょう
  • 携帯やパソコンの時計で日常ほとんど事が足りてしまう私のような人は、特に要注意です。被験者の方に焦燥感を与えずに時間配分ができるよう、腕時計を持っていきましょう
  • ウェブに毒されて生活している私のような人は、特に要注意です。被験者の方がお使いになるブラウザやパソコンの設定は、できる限り初期状態に戻しておきましょう。私は英語キー配列を使っていますので、テストのたびに日本語配列に戻しています。ブラウザでは、いつもお使いのものをお聞きして使っていただきますが、プラグインなどが被験者の方にとって予想外の動きを始めないように、disableしておいたりします。
などなど・・・。

------
以上、細かい点で、実際のテストの方法とはあまり関係がないのですが、あたふたしないユーザビリティテストの実施のためにお役立てください。

2007年9月20日

Webブラウジングを快適にするAutoPagerize
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちわ、山下です。
Software Design 10月号にサーバ監視ツールCactiについての記事を書きました。この号のNagios,Swatch,Wiiの記事もウノウ・エンジニアによるものですので、ぜひ興味のある方はご覧ください。

さて、今日はWebブラウジングを快適にするAutoPagerizeというツールを紹介します。AutoPagerizeはGreasemonkey用の拡張スクリプトで、今、自分のまわりで爆発的に流行っています。先日の出張 Shibuya.js 24でも作者の方が発表されていたのでご存じの方も多いのではないでしょうか。

念のために何をするツールかを説明しておくと、Googleなどの検索結果で次のページを表示するときに「次へ」ボタンを押さなくてもページ下部までスクロールすると、勝手に次のページを読み込んでその下に付け足してくれるものです。なかなか文章では伝わらないかもしれませんが、この操作感を一度味わってしまうともう元の生活には戻れなくなります。

Firefox専用なので注意が必要ですが、インストール方法を説明します。

1.Greasemonkeyを下記のページからインストール

https://addons.mozilla.org/ja/firefox/addon/748

2.AutoPagerizeをインストールする

http://userscripts.org/scripts/show/8551
(右サイドの「install this script」をクリック)

3.対応サイトで使う


対応サイトは、Google、はてな、tumblr、Twitter、flickr、Yahoo、ニコニコ動画、livedoorなど現時点で約200個のサイトに対応しています。
私も映画生活レビュー用のSITEINFOを作ってみました。

url:          http://www\.eigaseikatu\.com/imp/\d+/\d+/
nextLink:     (//ul[@class="commonPrevNextNavi"]/li[@class="next"]/a)[last()]
insertBefore: id("mainImpPostform")
pageElement:  id("mainImpEntry")/*[not(self::ul[@class="commonPrevNextNavi"])]

urlを正規表現、次ページへのリンクと挿入位置をXPath式で記述します。spam対策のため、SITEINFO用のWikiに書き込めなくなっていたのですが、そのうち使えるようになるかと思います。 登録して頂きました。どうもありがとうございます!



2007年10月22日追記:
また、AutoPagerizeに必要な情報をHTMLにMicroformats形式で埋め込んでおけば、SITEINFOがなくてもAutoPagerが起動するようになります。HTMLを直接いじれる環境にいる人は、SITEINFOに追加するよりも自分でHTMLを修正したほうがよいでしょう。

以下、作者様のページより抜粋。


  • link要素またはa要素でrel="next" (複数あった場合は、先に現れるものが選ばれます)

  • classでautopagerize_insert_before (複数あった場合は、先に現れるものが選ばれます)

  • classでautopagerize_page_element (複数指定可、指定したもの全てが挿入されます)

<html>
  <body>
    <div class="autopagerize_page_element">
      <div>foo foo foo</div>
      <div>foo foo foo</div>
      <div>foo foo foo</div>
      <div>foo foo foo</div>
      <div>foo foo foo</div>
    </div>
    <div><a href="ap_micro2.html" rel="next">next</a></div>
    <div class="autopagerize_insert_before">footer</div>
  </body>
</html>

2007年9月19日

mecabのユーザ辞書を追加した時にはまったメモ
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは satoです。mecabのユーザ辞書を追加したのですが,いろいろはまったので誰かがはまった時のためにメモっておきます。

1)品詞IDの取り方

2)ユーザ辞書の追加方法

3)禁止語句とか独自の要素を付けたい

 csvファイルの最後に追加すればOK

4)context_id.cpp(88) [it != left_.end()] cannot find LEFT-... ってエラーが出る

 left-id right-id の文字コードが csvファイルの文字コードと違うのが原因 nkf --utf8とかで文字コードをそろえる

5)CHECK_CLOSE_FALSE(sysdic->isCompatible(*d)) << "incompatible dictionary: " << _dic[i]; って出る

 sys.dic と ユーザ辞書の文字コードが違うと出るエラー utf8とUTF8は違うと判定されるのでutf-8と書く

6)追加した辞書に品詞IDが付かない or 65535になる

 pos-id.def の文字コードと ユーザ辞書の文字コードが違う。nkf --utf8とかで文字コードをそろえる

2007年9月18日

BTSとテストケース管理システムを連携させる
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは!やまもと@テスト番長です。
最近TEF(Testing Engineer's Forum)-ソフトウェアテスト技術者交流会 の有志によって日本語化されたTestLinkというオープンソースのテストケース管理ツールがあるのですが、同じくオープンソースのBTSであるMantisと連携させる方法が紹介されています。

TestLinkとバグ管理システムMantisの統合http://swproject.g.hatena.ne.jp/keyword/TestLink%e3%81%a8Mantis%e3%81%ae%e7%b5%b1%e5%90%88

BTSは導入していても、テストケースは管理されていないか、Excelなどでリストを編集しているケースは非常に多いと思います。
テストケース管理システムは使い慣れないと煩雑に感じる反面、ある程度以上テスト項目を洗練させるには是非導入したいツールです。
ウノウではエンジニアサイドの使い勝手を考えて主にTracを使用しているのでこちらのツールは導入できないのですが(TracではTestCaseManagement Pluginなんかがありますね)、Mantisをお使いの方は是非導入を試されてはいかがでしょうか?

TestLink
http://testlink.org/wordpress/

TestLink日本語化プロジェクト
http://www38.atwiki.jp/testlink/pages/1.html

Mantis
http://www.alles.or.jp/~sogabe/mantis/

2007年9月17日

DRBDで2TBのハードディスク容量を使う方法
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは、naoyaです。

先日、フォト蔵のサーバのハードディスク空き容量が減ってきたので、ハードディスクを500GBx4から1TBx4のハードディスクに交換しました。

フォト蔵のサーバでは、以前satoが紹介したようにDRBDを使って写真や動画のデータを相互バックアップしています。

フォト蔵のサーバのハードウェアとソフトウェア環境は、次のとおりです。

  • HDD: 1TB x 4
  • OS: Fedora Core 5(2.6.20)
  • DRBD: 0.7.24

ハードディスクは、二本ずつソフトウェアRAID0で組んでいます。

# cat /proc/mdstat
Personalities : [raid0]
md1 : active raid0 sdb1[0] sdc1[1]
      1953519872 blocks 64k chunks

md0 : active raid0 sda3[0] sdd1[1]
1943639872 blocks 64k chunks

unused devices:


この状態で、次のコマンドでDRBDを起動させてみました。

# /etc/init.d/drbd start

そうすると、次のエラーメッセージが表示されてしまいました。

/etc/init.d/drbd start
Starting DRBD resources:    [ d0 d1 d2 ioctl(,SET_DISK_CONFIG,)
failed: Cannot allocate memory
...

調べてみると、vmalloc sizeを192m割り当ててると、うまくいくそうなので、grubの設定ファイル(/etc/grub.conf)に、次の内容に変更してみました。

...
  title Fedora Core (2.6.20-1.2320.fc5smp)
  root (hd0,0)
  uppermem 524288
  kernel /vmlinuz-2.6.20-1.2320.fc5smp ro vmalloc=192m root=LABEL=/
  initrd /initrd-2.6.20-1.2320.fc5smp.img
...

vmallocの設定は、rootより後ろに書けないので注意してください。

この設定で、初めてDRBDで2TBのハードディスク容量を使うようにすることができました。


最初、移行するとき、DRBD上のファイルシステムはXFSにしていたのですがbonnie++でハードディスクに負荷をかけると落ちてしまうので、従来通りext3に変更して現在稼働中です。

次は、現在satoがDRBD 8系でのprimary/primaryの構成を試しているようなので、フォト蔵のサーバにも組み込めるか検討したいと思っています。


2007年9月13日

内部から見た「ウノウ」という会社
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

今月3日付けでウノウに入社したshimookaです。よろしくお願いします。

入社から2週間経ち、社内や仕事に少しずつ慣れ始めた感じなところです。
ウノウは2001年設立のベンチャーです。前職もベンチャーのSI会社でしたが、ウノウを外部から見ていて「通常の会社と何か違う」と思っていました。入社してそれが垣間見えた感じがしています。
ということで、今回は「内部から見たウノウ」というテーマでまとめてみました。

社内の雰囲気

「音楽とキーボードを打つ音が絶えず聞こえている」、まさにこの通りです。音楽の音量は比較的大きめ。その中で、黙々と何かをこなしている感じです。会話はあまり聞こえてきません。また、半分ぐらいの人がヘッドフォンをして、別の音楽を聴いていたりします。 選曲は完全に「メンバーの趣味」です。J-POPあり、JAZZあり、洋楽あり、懐メロあり・・・なぜか、ルパン三世のテーマやドリフもあったりします。

情報共有

事務的な情報はWiki、プロジェクトの情報はWikiとメーリングリスト・BTS(バグトラッキングシステム)でほぼ共有されています。

また、Wikiとは別に社内Blogも用意されています。Wiki・Blogには個人のページも設けられていて、日報や週報、技術的な情報、雑記などを書き留めておくことができます。また、この週報を元に毎週全体ミーティングが行われています。以下、社内Blogの例です。

社内Blog
社内Blog posted by (C)shimooka

当然ながら、これらの情報は全て社内にオープンされていますので、コメントやトラックバックも行われます。やはり、技術的な情報の方が喰い付きが良いようです。

ランチデー

くじ引きで社員をグループに分け、グループ単位に食事に出かけます。隔週火曜日の13時から行われています。 ある程度の規模以上の組織になるとどうしてもグループ化してしまい、組織としての柔軟性が損なわれてしまいがちです。そこで「くじ引き」で決め毎回違うメンバーと食事に行くことで、メンバー同士の交流や情報交換を活性化しようというのが狙いです。私は入社してまだ1度しか行っていませんが、意外とプロジェクト以外の話が弾みました。

同じように問題と感じている方は是非お試しを ;-)

コードレビュー

毎週木曜日の夕方に1時間ほど行われています。持ち回りで、関わっているサービスや案件のほか、様々なネタをプレゼンしていきます。 本当にこの時間は濃厚です。本当に「技術の会社」という感じがします。

なお、コードレビューの様子はビデオで撮影されています。内容によってはフォト蔵に公開されています。ユーザー「フォト蔵」のアルバムをチェックしてみてください。

まとめ

やはり、内部から見ても「通常の会社と何か違う」会社でした。

今までウノウの外からラボブログを読んでいましたが、いざ自分が書く番になると難しいですね。次は何か技術ネタが出せればと思いますので、今後ともよろしくお願いします :-)

正規表現の勉強法
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

bokkoです。

テキストの整形処理を行う上で正規表現は非常に重要です。正規表現なしでテキストの整形処理をするくらいなら自分で正規表現ライブラリを書いてやるという人がいるのかどうかは知りませんが、実際、相当大規模なWebソフトウェアのプロジェクトならそっちの方が早いかもしれません。なんだか自分で書いてて何が言いたいのかわからないような文章になってしまいましたが、私が言いたいのは正規表現はそれくらい強力で、習得して使う価値があるということです。

ただ、正規表現にはじめて触れる場合、非常にとっつきにくく思う方もいると思います。私も最初、意味のわからない文字の羅列に圧倒されたような気がします。

このように最初の段階でとっつきづらく思ってしまうのは大抵慣れの問題なので、とりあえず、正規表現を使ったプログラムでも書いてみるのが一番の近道です。何かしらのテキストを整形するというのがいい題材ですが、私が特にオススメするのはWikiなどのパーサを書くことです。
私は去年、とあるWikiクローンを作成したのですが、当初の目的は、「正規表現を勉強すること」でした。(後で変わりました)
実際、これのおかげで私は正規表現に対するアレルギーもすっかりなくなり、テキスト整形のプログラムを書くときだけでなく、Emacs上やターミナル上で検索する際にも正規表現を積極的に使うようになりました。

さすがにプログラミング言語のパーサを正規表現だけで書くのは無理があると思いますが、簡単なWikiのパーサであれば、正規表現が中心のプログラムでも結構書けます。(多段リストのように複数の入れ子からなる文法を正規表現だけで書くのは難しいですが)

URLをAタグで囲んだり、*で始まった行はリストに変換したり、「引用」をblockquoteタグではなく、何か別の記号で記述できるようにしたりするなど、Wikiパーサの作成には正規表現を勉強するための題材がたくさん転がっています。

もしくは、大学生なら所属しているサークルの名簿のテキストファイルやCSVファイルを正規表現を使って解析して、データベースに突っ込んだりするというのも前者ほどではないかもしれませんが、いい勉強になると思います。(私は実際やりました)
また、普段からgrepやegrepを積極的に使うことを心掛けるのもいいと思います。


ただ、やっぱり詳しく解説されている書籍がほしいところです。単に正規表現を使うだけでは、その動作原理を理解したり、その場その場で最適な正規表現を適用するのは難しいと思います。しかし、正規表現の動作原理まで扱った書籍はあまりありません。中には正規表現の動作原理がわかっていないと、どう使ったらいいのかわかりにくいものもあります。実際、アトミックグループなどは正規表現の動作原理を、ある程度理解していないと、なんでそんなのが必要なのかピンと来ないと思います。

そういった要求を満たしてくれる数少ない書籍が「詳説 正規表現(第二版)」です。

この本には、私が去年、Wiki文法のパーサーを書く過程で調べて身に付けたほぼすべてのことと、もっとうまくやるための方法と、何故そうすべきなのかということと、そして、初心者(つまり1年前の自分)が犯すであろうあらゆる過ちを回避するための方法が書いてありました。

理論が先か実践が先か?

私の場合は、Wiki文法のパーサを書いて正規表現に慣れてからこの本を読んだため、
割とすんなり読めて、正規表現に対する理解も深まったと思っているのですが、
この辺は人によって好みが別れると思うので、自分に合う形で学習するのがいいと思います。

そして、本当に理解しようと思うなら両方やるべきです。

詳説 正規表現 第2版
詳説 正規表現 第2版
posted with amazlet on 07.09.13
Jeffrey E.F. Friedl 田和 勝
オライリー・ジャパン (2003/05/26)
売り上げランキング: 25292
おすすめ度の平均: 5.0
4 内容は大満足だけど高いです
5 噛めば噛むほど
5 正規表現の仕組みが分かります

2007年9月12日

MP4/3GPP/3GPP2ファイルフォーマットの基礎知識
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

isogawaです。

MP4や、その派生である携帯電話向けの3GPP、3GPP2などのファイルフォーマットはボックス(あるいはその基になったQuickTimeでの用語のAtom)と呼ばれるデータブロックで構成されます。ボックスによってはその内部にさらにボックスが入れ子になるツリー構造になっています。

以下はNTTドコモの「MP4対応iモーション」ファイル(3GPP)の冒頭部分です(16進コードと文字が混在してて気持ち悪いあたりは気にしない)。

ADDR+0+1+2+3+4+5+6+7+8+9+A+B+C+D+E+F
00000000001Cftypmmp400000001
0010mmp43gp53gp400000588
0020moov0000006Cmvhd00000000

各ボックスはその先頭8バイト(オクテット)がボックスを識別するためのヘッダで、最初の4バイト(オクテット)がボックスのサイズ、続く4バイトがそのタイプです。

上の例では先頭の4バイトが0x1Cなので、最初のボックスは0x00~0x1Bまで、続く4バイトの「ftyp」がそのタイプです。ftypボックスの中身は具体的なデータですが、続くmoovボックスは、さらにmvhdボックスを含む構成になっています(そして実際にはこの後さらに複数のボックスが入れ子になっています)。

+--ftyp
+--moov
| +--mvhd
| +--drm
| | +--dcmd
| +--trak
| | +--tkhd
| | +--mdia
| |   +--(以下省略)
| +--trak
| | +--tkhd
| | +--mdia
| |   +--(以下省略)
| +--udta
|   +--titl
+--mdat

このMP4対応iモーションファイルのボックス構造をツリー形式で表すとこんな感じですが、MP4ファイルフォーマットではある程度自由に任意のボックスを組み合わせることができるため、ファイルによってその構造はまちまちです。

The MP4 Registration Authorityに登録されているボックスの一覧は以下で参照できますが、これ以外のボックスが存在している場合もあります。

主要なボックスには、、ファイル種別を示すftyp、動画や音声の実際のデータを格納したmdat、各種メタデータを格納したさまざまなボックスのコンテナ(入れ子の親)であるmoovなどがあります。

ftyp

ftypボックスには、そのファイルが準拠している規格を表す4文字の「ブランド」が列挙されます。タイプ名の「ftyp」の直後に続く4文字は「(The major or ‘best use’) Brand」で、そこから4バイト空けて、以降ボックスの最後までの文字列を4文字ごとに区切ったそれぞれが「Compatible Brand」です。

上のMP4対応iモーションの例ではこれは以下のようになります。

  • Major Brand: mmp4
  • Compatible Brand #1: mmp4
  • Compatible Brand #2: 3gp5
  • Compatible Brand #3: 3gp4

プレイヤーによってはこの内容で再生の可否を判断しており、この内容を書き換えることで、本来は再生対象ではないファイルを再生する手法はあちこちで見かけます。

ハードウェアベンダーはこれらのブランドに独自の値を採用しているケースが多々あり、そのようなケースではブランドだけで、それがどの機器用のファイルであるか判別できます。例えば、NTTドコモのMP4対応iモーションは「mmp4」、auのEZムービーは「kddi」、ソニーのPSPは「MSNV」といった値をそれぞれMajor Brandに採用しています(ソフトバンクモバイルの場合、Compatible Brandに「vfj1」という値が含まれるようです)。

moof

3GPPと比較した3GPP2の特徴のひとつに「ムービーフラグメント」があります。これはHTTPストリーミングを想定したもので、文字通りデータを分割し小分けに受信することで、全データのダウンロードを完了しなくても再生が開始できるというものですが、これはファイルフォーマット的には、以下の図にみられるように、moofボックスによって実現されています。

ちなみにauのEZムービーでは最大15秒ごとに分割されますが、ムービーフラグメントに対応していないプレイヤーでは最初の15秒しか再生されなかったりします。

ボックス構造を表示するツール(for Linux)

MP4Box

オープンソースのマルチメディアフレームワークGPACに付属するコマンドラインアプリケーションです。ボックス構造の表示は以下のような感じ。

$ mp4box -std -diso hoge.3gp

<?xml version="1.0" encoding="UTF-8"?>
<!--MP4Box dump trace-->
<IsoMediaFile Name="hoge.3gp">
<FileTypeBox MajorBrand="mmp4" MinorVersion="1">
<BoxInfo Size="28" Type="ftyp"/>
<BrandEntry AlternateBrand="mmp4"/>
<BrandEntry AlternateBrand="3gp5"/>
<BrandEntry AlternateBrand="3gp4"/>
</FileTypeBox>
<MovieBox>
<BoxInfo Size="1416" Type="moov"/>
<MovieHeaderBox CreationTime="-1023342072" ModificationTime="-1023342067" TimeScale="60000" Duration="282000" NextTrackID="3">
<BoxInfo Size="108" Type="mvhd"/>
<FullBoxInfo Version="0" Flags="0"/>
</MovieHeaderBox><TrackBox>
(以下省略)

mp4dump

オープンソースのストリーミングサービス構築パッケージMPEG4IPに付属するコマンドラインアプリケーションです。ボックス構造の表示は以下のような感じ。

$ mp4dump hoge.3gp

Dumping hoge.3gp meta-information...
 type ftyp
  majorBrand = mmp4
  minorVersion = 1 (0x00000001)
  <table entries suppressed>
 type moov
  type mvhd
   version = 0 (0x00)
   flags = 0 (0x000000)
   creationTime = 3271625224 (0xc3010a08)
   modificationTime = 3271625229 (0xc3010a0d)
   timeScale = 60000 (0x0000ea60)
   duration = 282000 (0x00044d90)
   rate = 1.000000
   volume = 1.000000
(以下省略)

getID3()

MPEG-4に限らずさまざまなメディアファイルのメタデータを気合いで抽出するPHP用クラスライブラリです。デモページで動作イメージを確認できます。

uuidボックスの解析

ボックスによっては、その中身の仕様が一般には公開されていないものがありますが、特にベンダーが独自の拡張を施すために用意されたuuidボックスの中身は、当然ベンダーごとに異なります。こうしたボックスはこれまでにもさまざまな解析が試みられてきました。以下はauのEZムービーやPSP動画のuuidを解析した先達の例です。

しかし、特にEZムービーについては某巨大掲示板のスレにもっとも情報が集約されているかもしれません…。

基礎資料

関連記事

2007年9月10日

「サーバサイドCSS」という選択
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

miyakeです。突然ですが、CSSって書くの面倒ですよね。何らかのプログラミング言語を知っている人間から見ると、CSSというのは言語としてはかなり貧弱です。

もちろんCSSはプログラミング言語では無いので、それを貧弱だと言われてもCSSもいい迷惑かも知れません。でも、

div#content { ~ }
div#content div.entry { ~ }
div#content div.entry p { ~ }
div#content div.entry ul { ~ }

こういう大したことをしていないのに、コードがどんどん長くなっていくのを見ると、もう少し何とかならないものかと思ってしまいます。

コピペするにしても、

  1. コピー元にカーソルを移動
  2. 範囲選択してコピー
  3. コピー先にカーソルを移動
  4. ペースト

という操作が必要になります。数が増えてくるとコピペのミスも発生しかねませんし、idやclass名が変わった時の対応も面倒です。まとめて置換して、想定外の部分が書き換わってしまわないとも限りません。

それに、変数も使えないので同じテキストを何度も何度も書く(コピペする)ことになります。プロパティの定義ならまだしも、画像のパスやカラーコードは変数に書けたらなぁと思ったことのある人も少なくないのではないでしょうか。

そもそもCSSの利点として「再利用性が高く、メンテナンスコストを抑えられる」というのがあるはずです。確かに外部CSSファイルにすることで、CSS以前に比べれば随分と効率化されました。しかし、CSSそのものの記述の効率化については今まで目を向けられて来なかった分野ではないかと思います。

サーバサイドCSS

CSSはブラウザが解釈するものなので、最終的にブラウザが読み込むCSSの仕様と挙動そのものを変えることは出来ません。そこで、CSSをサーバサイドで出力するというアプローチを採ります。こういった「サーバサイドCSS」といったようなアプローチはアイデアとしてはありがちだと思うのですが、あまり世の中にはないようです。

div#content { ~
    div.entry { ~
        p { ~ }
        ul { ~ }
    }
}

こんな感じで書けたらいいと思いませんか?

という訳で、手前味噌で恐縮ですが個人で公開している物のご紹介です。

Smart*CSSは、mod_rewrite+php+Smartyで動作するCSSのプリプロセッサです。技術的にはこれといって見所の無いものですが、自分ではなかなか便利に使えています。

今のところ、出来ることと言えば、

  • CSSの構造化(入れ子による記述)
  • 変数の使用

ぐらいで、まだまだなのですが、他にも

  • ブラウザを判定して、最適化したCSSを出力(自動CSS Hack)
  • ブラウザ毎のCSSをキャッシュ or 静的ファイル書き出しに対応
  • ありがちなパターン(Image Replacementなど)をモジュール化

といったところを実装して行けたらいいなと考えています。

普及していないのは何故?

個人的にはこういうツールはもっと普及していていいんじゃないかと思うのですが、現実はそうはいかないようです。

あくまで推測でしかないのですが、世の中のCSSを書く人は

  • Cドライブに保存して動かない物はいらない
  • Dreamweaverでそういう書き方出来ないし
  • 憶えるの面倒だからコピペするよ

みたいな感じなのかも知れません(Dreamweaverでどうなのかはよく知りませんが…)。とは言え、Ajaxの登場などもあり、エンジニアがHTMLやCSSを書くことも増えてきていると思われる昨今、サーバサイドでCSSを拡張するというのはもう少し日の目を浴びていいように思います。エンジニアたる者、生産性の低いキータイプは極力避けたいところです。もっと楽をするためのツールが出て来て欲しいと思いませんか?

まだまだ自分自身煮詰められていませんが、「サーバサイドCSS」というアプローチに可能性を感じて頂ければ幸いです。

最後に

という感じでこの記事を書いてから、ふと「サーバサイド css」でGoogle検索すると、こんな記事を見付けました。

こちらでCSS Server-side Pre-processorというツールが紹介されていました。今し方見付けたばかりで、中は見ていないのですが、どうやら自分が作った物と同様のコンセプトで作られているようです。あまり知られてはいないようですが、海外にはあるんですね… まぁ、自分もせっかく作ったから、もう少し育ててみますか…

2007年9月 8日

「視線を導く」方法あれこれ
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

yamazakiです。毎度毎度技術系なこのウノウラボには馴染まない話題を振りまいているわけですが、さてはてどの程度お役に立てているものか…。

今回のテーマは「視線」です。例えばWebサイトは「見てもらう」ことが大事ですし、また、UI設計の場面などでも、「視線をうまく導いてやる」のは使い勝手をよくする上でも大切なことだと思います。といったわけで、ユーザの視線をうまく捕まえてみたり導いてみたりする上で役に立つかもしれない情報を簡単にまとめてみます。


1.視線をつかまえる

Web上にも街にも広告だの何だのと情報が氾濫しています。その中で「見てもらう」ためには何かしらの工夫が必要になることがあります。というわけでまずは「人の視線をつかまえる」ための方法論を少し。

「人の顔」を使う

まずはこの画像を見てみてください。

01
01 posted by (C)フォト蔵

最初に、どれを「見てしまいました」か?
「人の顔」が最初に目に入った、という方が多いのではないでしょうか。
たとえばずらりと色々な写真が並んでいて、その中に人の顔の写真が1枚混じっていると、なぜかそれが最初に目に入りやすいです。
人が生きていくうえで「人の顔を見る」ということは大事なことなのでできるだけ早く認知するようになってるのかな、と思うのですが詳しい理由はよくわかりません。
とりあえず、ふらふらした視線を最初にキャッチしたいときには、人の顔の写真を使うとわりとつかまえやすい、というのを覚えておくと、広告などを作る際に役に立つことがあります。

その他目立たせる方法

UIなどを作る際に、「このボタンは特別だからちょっと目立たせたい」とか、「この部分はよく理解してほしいから読んでほしい」とか、他よりも優先的に見てもらいたい部分を作りたい、といったことは結構あるものです。そんな時に「他と区別して目立たせる」方法としてたとえばこんなやりかたがあると思います。

04
04 posted by (C)フォト蔵

他にも視線をキャッチする方法として「目立つ」ために「派手な動き」や「点滅」なんていうのもあるのでしょうが、この辺りは嫌がられるような事も多いので割愛。


2.視線を導く

続いて、視線をどこかに導くための方法です。
広告寄りのWebサイトでは文章などを読んでもらうために。また、UIなどでは操作に関わるものが自然と目に入るとよいと思いますし、そういったことを実現する視線をうまく誘導してやるための方法論です。

「人」の視線

他の人が見ているもの、って何だか妙に気になることありませんか?
実際、無意識的に誰かが見ている方向と同じ方向をつい見てしまう、ということが結構あります。
これもデザインの場面で応用できます。
たとえば読んでほしいコピーがあるとして、その横にそれを見ている形で人の写真や人のイラストを置くと、そのコピーに視線が自然に移動しやすくなります。


02
02 posted by (C)フォト蔵

矢印を使う

矢印には、なぜかその矢印の向いている方向にあるものを見てしまう、という効果があります。
これについては説明するよりも例を見ていただいたほうが早いと思うので画像をご覧くださいませ。

03
03 posted by (C)フォト蔵


何となく矢印の向いている方向にあるものを見てしまうことが多いと思います。
ちなみに矢印は、必ずしも「→」みたいな形でなくても、何かしら先がとがったようなカタチであれば十分に役目を果たせます。


リズム、関連性、上下によって導く

視線を導く、という意味では、同じ形を規則的に並べる、などの方法で次々に視線を送ることもできると思います。たとえばこんな感じ。

05
05 posted by (C)フォト蔵

同じ形のもの、同じ規則性で並んでいるものは「一連のつながりのあるもの」と認識されやすいです。また、水や物体の運動の関係で、上下だと「上から下」に流れていくのを「自然」だと感じやすいので、関連のあるものを上から下へ並べたりして視線を導く方法があります。


他にもあれこれと視線をうまく導く方法があると思います。色々研究して、使い勝手のよい魅力的なWebサイトなどを作り上げていきましょう!

2007年9月 6日

PHPでSSL通信する時の注意点
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

yukiです。
今回はPHPでSSL通信したい時の注意点などを紹介します。

PHPでSSL通信を行う際には、

  • fsockopen
  • pfsockopen
  • file_get_contents
  • fopen
  • stream_socket_client

など様々であり、利用する場面がありますが、SSL通信が許可されている必要があります。
よくHTTP_Requestなどを利用してPOSTしたいがhttpsだとうまくいかない!という記事を見かけますが、参考になればと思います。

  • * allow_url_fopenが有効かどうか

上記の関数についてfile_get_contents・fopenでは上記設定が有効かを調べます。
php.iniで設定されていますが、通常デフォルトで使用可能なのですが、レンタルサーバーなどでは使えないこともありますので調べてみましょう。
phpinfo()関数を利用するか、コマンドラインでは
php -i | grep allow_url_fopen

で知ることが出来ます。
またphp4.0.3以前では
--disable-url-fopen-wrapper

でコンパイルされていると利用不可となっています。

  • サポートされているプロトコル・ラッパーを調べる
通信する際のプロトコル、及びラッパが使用可能かを調べます。この場合はhttps://(PHP4.3.0以降)が対象です。 phpinfo()ではRegisterd PHP Streamで確認できます。 SSL通信の場合OpenSSLがインストールされている必要があり、PHP4.3.0以降では静的にコンパイルされ組み込まれている必要がありますが、PHP5以降ではモジュールとしてコンパイルされていても使用できます。

レンタルサーバーなどでPHP4.3以降を利用し、かつOpenSSLサポートがなければ、管理者によってリコンパイルされなければなりません。

ですが、もしcurlコマンド(SSLが有効であること)が使える環境であれば、popen・proc_open関数から利用することもできます。(レスポンス速度等の問題はありますが)

なおOpenSSLのバージョンもPHPによって異なるので注意が必要です。

以下をまとめると次のような表になります。

PHP再コンパイルOpenSSL
~4.0.4×0.9.6~
4.0.5~4.2.3×0.9.5~
4.3.0~4.3.10.9.5~
4.3.2~0.9.6~
5.0.0~×0.9.6~

利用するなら、PHP5がお手軽なようです。

2007年9月 5日

Googleマップの地図を好きな写真に簡単にさしかえられるツール
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

matsuda です。こんにちは

最近Googleマップで独自の地図を表示したりするのがちょっとしたブームだったりしたので、 よーし僕も挑戦するぞーと思ったのですが、Googleマップって1枚の画像で 簡単に出来るものじゃなかったんだと挫折(ズームごとに256×256pxに分割された画像が必要のよう…)。 しかし、この問題を解決してくれるツールがあったので紹介します。

1枚の写真とズームレベルを決めればあっという間にGoogleマップに画像が表示 されて拡大縮小できる優れものです。
↓↓↓
The Google Maps Image Cutterよりダウンロードが可能

使い方はいたって簡単

1)メニューの左にあるFileから、地図にしたい写真を選ぶ
gimc001
gimc001 posted by (C)フォト蔵

2)最大のズームレベルを設定する
ズームレベルが大きいほど、出来あがるタイルの数が増えるのでほどほどがいいかと思います。Tile Count で枚数がわかります。 今回はズームレベル5(全部で341枚の画像になりますね)で作りました。
gimc002
gimc002 posted by (C)フォト蔵

3)後は[create]→[start] で作成されます。

4)作成が終わると、HTMLのファイル(今回はDSC_0009.html)と、分割された画像が納められたフォルダ(今回はDSC_0009-tiles)ができあがり。
gimc003
gimc003 posted by (C)フォト蔵

HTMLファイルの中の

http://maps.google.com/maps?file=api&v=2.x&key=PUTAPIKEYHERE

のGoogle Maps API keyを自分のものに代えて(Google Maps API keyはココから取得できます)tile画像のフォルダと一緒にアップすれば完成。
↓↓↓
gimc004
gimc004 posted by (C)フォト蔵

写真以外にも、会社案内用の地図とかを手書きにしたりとか…いろいろ使えそうですね。

Migrateのご紹介
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは、chihiroです。今回はデータベース・スキーマのバージョン管理ツールであるMigrateを紹介します。

Migrate
http://erosson.com/migrate/docs/index.html

インストール

開発版の方を使いますので、レポジトリからコードをチェックアウトしてインストールします。

$ svn co http://erosson.com/migrate/svn/migrate/branches/monkeypatch_removal migrate
$ cd migrate
$ python setup.py install

もしくは、easy_installで直接インストールします。

$ easy_install http://erosson.com/migrate/svn/migrate/branches/monkeypatch_removal

また、MigrateはPython製のORMであるSQLAlchemyをベースにしているので、こちらもインストールしておきます。

SQLAlchemyは近いうちにバージョン0.4系が正式版になると思われますが、今回は0.3.10でテストしました。

$ easy_install SQLAlchemy==0.3.10

レポジトリの初期化

インストールが完了すると、PythonのHomeディレクトリにbin/migrateというスクリプトがインストールされます。

最初に、このmigrateを実行してデータベース・レポジトリを初期化します。

初期化のコマンドは"create <レポジトリ名> <プロジェクトの説明文>"です。

$ migrate create db_repository "My blog project"

これでカレントディレクトリにdb_repositoryというレポジトリが作成されます。ここからは、ここで作成されたdb_repository/manage.pyというスクリプトを使って管理を行います。

まず、データベースにバージョン管理用のテーブルを作成します。

コマンドは"version_control <データベースのURL(DSN)>"です。

DSNについてはSQLAlchemyのドキュメントを参照してください。

$ python db_repository/manage.py version_control mysql://username:password@localhost/dbname

毎回DSNを指定するのは手間がかかるので、db_respository/manage.pyを書き換えて、このプロジェクトで使うDSNを指定しておくと便利です。

#!/usr/bin/env python
# db_repository/manage.py
from migrate.versioning.shell import main

main(repository='db_repository',
     url='mysql://username:password@localhost/dbname')

スキーマのバージョンを確認する

Migrateレポジトリのバージョン番号を調べるには、"version"コマンドを使います。

$ python db_repository/manage.py version
0

一方、データベース側のバージョン番号を調べるには"db_version"コマンドを使います。

python db_repository/manage.py db_version
0

スキーマ定義スクリプトを書く

まず"script"コマンドを使ってテーブル定義用のスクリプトを作成します。

$ python db_repository/manage.py script script.py

"script.py"というのはレポジトリにコミットされない一時ファイルなので、実際にはどんな名前でも構いません。

このscript.pyを編集し、SQLAlchemyの流儀でテーブルを定義します。

# script.py
from sqlalchemy import *
from migrate import *

def upgrade():
    user = Table('user',
                 Column('user_id', Integer, primary_key=True),
                 Column('username', Unicode(50), nullable=False),
                 Column('password', String(250), nullable=False),
                 mysql_engine='InnoDB',
                 )

    post = Table('post',
                 Column('post_id', Integer, primary_key=True),
                 Column('user_id', Integer, ForeignKey(user.c.user_id), nullable=False),
                 Column('title', Unicode(250), nullable=False),
                 Column('content', Unicode, nullable=False),
                 Column('created_at', DateTime, nullable=False),
                 Column('updated_at', DateTime, nullable=True),
                 mysql_engine='InnoDB',
                 )

    user.create(migrate_engine)
    post.create(migrate_engine)

def downgrade():
    migrate_engine.execute("DROP TABLE post")
    migrate_engine.execute("DROP TABLE user")

upgrade()関数にアップグレード時の操作を、downgrade()関数にダウングレード時の操作を書きます。

すべてのスキーマ操作をSQLAlchemyを使ってPython風に書くのが理想なのでしょうが、僕の場合は、「CREATE TABLEだけはSQLAlchemyを使って書き、それ以外の操作をmigrate_engine.executeを使ってSQLを直接発行する」というような使い方をしています。

スクリプトが完成したならば、テストを行います。

$ python db_repository/manage.py test script.py
Upgrading... done
Downgrading... done
Success

アップグレード、ダウングレード共に問題ないことを確認した上で、レポジトリにコミットします。

$ python db_repository/manage.py commit script.py

コミットすると、db_repository/versionsディレクトリに"<バージョン番号>/<バージョン番号>.py"として先ほどのスクリプトがコピーされます。

"version"コマンドでレポジトリのバージョン番号を確認してみましょう。

$ python db_repository/manage.py version
1

アップグレード/ダウングレード

データベースのスキーマのバージョンを上げるには"upgrade"コマンドを使います。

$ python db_repository/manage.py upgrade
0 -> 1...  done

"db_version"コマンドでデータベースのバージョン番号を確認してみます。

$ python db_repository/manage.py db_version
1

パラメータなしで"upgrade"コマンドを実行した場合、レポジトリの最新のバージョン番号まで自動的にアップグレードが行われます。もし、ある特定のバージョンまでアップグレードを行うときには、--versionパラメータを指定します。

$ python db_repository/manage.py upgrade --version=1
0 -> 1...  done

ダウングレードする場合は、"downgrade"コマンドを使います。この時は、--versionパラメータでバージョン番号を明示的に指定しなくてはなりません。

$ python db_repository/manage.py downgrade --version=0
1 -> 0...  done

Migrateを使った開発サイクル

ここまでのところを簡潔にまとめると、Migrateを使った基本的な開発サイクルは次のようになるでしょう。

$ python db_respository/manage.py script script.py
$ python db_respository/manage.py test script.py
$ python db_respository/manage.py commit script.py
$ python db_respository/manage.py upgrade

余談ですが、この手のマイグレーションツールを初めて使い始めたとき、「ダウングレードってするの?」という疑問を抱くときがあります。個人的には、ダウングレードは結構使用しています。ただ、ほとんどが「アップグレードのスクリプトやスキーマ設計にミスがあった」という場合なので、開発スタイルや性格の問題なのかもしれませんが・・・

MigrateのTips

複数のURLを扱う

開発版データベースと本番データベースのように複数のデータベースを管理する必要がある場合、"manage"コマンドを使って、管理用スクリプトを複数作っておくと便利です。

$ python db_repository/manage.py manage db_manage.py \
  --url=mysql://username:pass@localhost/db \
  --repository=db_repository
$ python db_repository/manage.py manage db_manage_dev.py \
  --url=mysql://username:pass@localhost/db_dev \
  --repository=db_repository

以降は、db_manage_dev.pyを使用して開発を行い、本番環境ではdb_manage.pyを使用してスキーマの管理を行います。

カラムの追加

公式のドキュメントの説明では、migrate.changesetを使ってカラムの追加、変更、削除を行えるとされていますが、正しく動作している感じがしません。

僕の場合は、migrate_engine.executeを使って直接ALTER文を発行しています。

まとめにかえて - Migrateの問題点

残念ながら、このツールは開発が止まってしまい、今後の動向は不透明です。また、エラーメッセージが非常に分かりにくいという問題点もあります。

しかし、現在僕が開発しているアプリケーションもこのMigrateに依存しており、このまま死なせるにはあまりにも惜しいツールですので、僕自身がメンテナンスに名乗りを上げるかもしれません。そういった意味で、あえてこの場で紹介させていただきました。

2007年9月 4日

Mac OS X上のUnicode
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

yamaokaです。

フォト蔵のアップロード画面では、 選択されたファイルのパスから拡張子を除いたファイル名だけを取得、 写真のタイトルの初期値に設定するということをしています(JavaScriptで)。

ある日、ユーザ様からお問い合わせがありました。 「サンバの季節.jpg」というファイルをアップロードしたところ、 タイトルが「サンハ」になってしまったとのこと。 Mac OS Xで、ブラウザはSafariをお使いでした。 同じ環境で試してみたところ、再現性を確認。 画面上ではタイトルに「サンバの季節」と設定されているにも関わらず、 実際に登録されるタイトルは「サンハ」になってしまいます。

問題が発生するのはタイトルをファイルのパスからJavaScriptで編集した場合だけで、 手で「サンバの季節」と直接入力した場合は、 何も問題なく登録処理を行うことができました。

Macでフォト蔵にアップロード
Macでフォト蔵にアップロード posted by (C)フォト蔵

Mac OS Xでは、ファイルシステムでUnicodeが用いられています。 いわゆるUTF-8なのですが、一般に言うところのUTF-8とは違います。 Macをお持ちの方は、「iconv -l」とTerminalからコマンドを実行してみてください。 「UTF-8-MAC」という表示が見つかるかと思います。

一般的なUTF-8では、文字はNormalization Form C(NFC)で 符号化正規化されています。 しかし、Mac OS Xのファイルシステムで用いられているUTF-8 (以下、便宜的にUTF-8-MACと呼びます)では、 文字はNormalization Form D(NFD)で符号化正規化されます。 日本語を扱う際にNFCとNFDでどこが違うかというと、 ひらがな・カタカナの濁点・半濁点の扱いです。 UTF-8では「が」は「U+304C」(HIRAGANA LETTER GA)ですが、 UTF-8-MACでは「U+304B U+3099」(HIRAGANA LETTER KA、COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK) となります。Appleによる簡単な説明は以下のページにあります。

しかもややこしいことに、UTF-8で濁点をあらわすコードは「U+309B」(KATAKANA-HIRAGANA VOICED SOUND MARK)で、 UTF-8-MACから送信される「U+3099」(COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK)とは別物です。 サーバ側できちんと変換できればよいのですが、 PHPのmbstringモジュールはUTF-8-MACをサポートしていません。 (Javaの場合、Java SE 6から導入された java.text.Normalizerクラス を使えば一発で変換できますね。)

また、Mac上で動作する他のブラウザの挙動を調査したところ (「サンバの季節」というファイルをタイトルを変更せずにアップロードした場合)、 それぞれの挙動が異なることがわかりました。

ブラウザフォーム上のタイトル表記登録されたタイトル
Safariサンバの季節サンハ
Firefoxサンバの季節サンバの季節
Operaサンハ゛の季節サンハ

Firefoxは内部的に変換処理を行うようになっているようです。 問題はSafariとOperaですね。 選択されたファイルのパスからJavaScriptで ファイル名を抜き出してタイトルに設定する部分で、 正しく扱えるような文字コードに変換することにしたいと思います。

基本的な流れとしては、UTF-8-MAC特有の「U+3099」(COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK)、 「U+309A」(COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK)がファイル名に含まれている場合は、 その前の文字と結合して濁音・半濁音の文字にしてあげればいいでしょう (ひらがな・カタカナのみの暫定的な対処に過ぎませんが)。

変換用の文字テーブルを用意して、逐一変換していくかたちにしたいと思います。

というわけで、ライブラリを作りました。以下のように使用します。

var path = document.getElementById("yourfile").value();
if (Photozou.MacUnicode.isNFD()) {
  // MacのSafari、Operaの場合だけ変換
  path = Photozou.MacUnicode.normalizeToNFC(path);
}

以下、ライブラリのコードです。

if (typeof Photozou == "undefined") {
  Photozou = {};
}

Photozou.MacUnicode = {};

Photozou.MacUnicode.combinedKana = [
  0x304C,  // HIRAGANA LETTER GA
  0x304E,  // HIRAGANA LETTER GI
  0x3050,  // HIRAGANA LETTER GU
  0x3052,  // HIRAGANA LETTER GE
  0x3054,  // HIRAGANA LETTER GO
  0x3056,  // HIRAGANA LETTER ZA
  0x3058,  // HIRAGANA LETTER ZI
  0x305A,  // HIRAGANA LETTER ZU
  0x305C,  // HIRAGANA LETTER ZE
  0x305E,  // HIRAGANA LETTER ZO
  0x3060,  // HIRAGANA LETTER DA
  0x3062,  // HIRAGANA LETTER DI
  0x3065,  // HIRAGANA LETTER DU
  0x3067,  // HIRAGANA LETTER DE
  0x3069,  // HIRAGANA LETTER DO
  0x3070,  // HIRAGANA LETTER BA
  0x3073,  // HIRAGANA LETTER BI
  0x3076,  // HIRAGANA LETTER BU
  0x3079,  // HIRAGANA LETTER BE
  0x307C,  // HIRAGANA LETTER BO
  0x3071,  // HIRAGANA LETTER PA
  0x3074,  // HIRAGANA LETTER PI
  0x3077,  // HIRAGANA LETTER PU
  0x307A,  // HIRAGANA LETTER PE
  0X307D,  // HIRAGANA LETTER PO
  0x3094,  // HIRAGANA LETTER VU
  0x30AC,  // KATAKANA LETTER GA
  0x30AE,  // KATAKANA LETTER GI
  0x30B0,  // KATAKANA LETTER GU
  0x30B2,  // KATAKANA LETTER GE
  0x30B4,  // KATAKANA LETTER GO
  0x30B6,  // KATAKANA LETTER ZA
  0x30B8,  // KATAKANA LETTER ZI
  0x30BA,  // KATAKANA LETTER ZU
  0x30BC,  // KATAKANA LETTER ZE
  0x30BE,  // KATAKANA LETTER ZO
  0x30C0,  // KATAKANA LETTER DA
  0x30C2,  // KATAKANA LETTER DI
  0x30C5,  // KATAKANA LETTER DU
  0x30C7,  // KATAKANA LETTER DE
  0x30C9,  // KATAKANA LETTER DO
  0x30D0,  // KATAKANA LETTER BA
  0x30D3,  // KATAKANA LETTER BI
  0x30D6,  // KATAKANA LETTER BU
  0x30D9,  // KATAKANA LETTER BE
  0x30DC,  // KATAKANA LETTER BO
  0x30D1,  // KATAKANA LETTER PA
  0x30D4,  // KATAKANA LETTER PI
  0x30D7,  // KATAKANA LETTER PU
  0x30DA,  // KATAKANA LETTER PE
  0x30DD,  // KATAKANA LETTER PO
  0x30F4   // KATAKANA LETTER VU
];

Photozou.MacUnicode.uncombinedKana = [
  0x304B,  // HIRAGANA LETTER KA
  0x304D,  // HIRAGANA LETTER KI
  0x304F,  // HIRAGANA LETTER KU
  0x3051,  // HIRAGANA LETTER KE
  0x3053,  // HIRAGANA LETTER KO
  0x3055,  // HIRAGANA LETTER SA
  0x3057,  // HIRAGANA LETTER SI
  0x3059,  // HIRAGANA LETTER SU
  0x305B,  // HIRAGANA LETTER SE
  0x305D,  // HIRAGANA LETTER SO
  0x305F,  // HIRAGANA LETTER TA
  0x3061,  // HIRAGANA LETTER TI
  0x3064,  // HIRAGANA LETTER TU
  0x3066,  // HIRAGANA LETTER TE
  0x3068,  // HIRAGANA LETTER TO
  0x306F,  // HIRAGANA LETTER HA
  0x3072,  // HIRAGANA LETTER HI
  0x3075,  // HIRAGANA LETTER HU
  0x3078,  // HIRAGANA LETTER HE
  0x307B,  // HIRAGANA LETTER HO
  0x306F,  // HIRAGANA LETTER HA
  0x3072,  // HIRAGANA LETTER HI
  0x3075,  // HIRAGANA LETTER HU
  0x3078,  // HIRAGANA LETTER HE
  0x307B,  // HIRAGANA LETTER HO
  0x3046,  // HIRAGANA LETTER U
  0x30AB,  // KATAKANA LETTER KA
  0x30AD,  // KATAKANA LETTER KI
  0x30AF,  // KATAKANA LETTER KU
  0x30B1,  // KATAKANA LETTER KE
  0x30B3,  // KATAKANA LETTER KO
  0x30B5,  // KATAKANA LETTER SA
  0x30B7,  // KATAKANA LETTER SI
  0x30B9,  // KATAKANA LETTER SU
  0x30BB,  // KATAKANA LETTER SE
  0x30BD,  // KATAKANA LETTER SO
  0x30BF,  // KATAKANA LETTER TA
  0x30C1,  // KATAKANA LETTER TI
  0x30C4,  // KATAKANA LETTER TU
  0x30C6,  // KATAKANA LETTER TE
  0x30C8,  // KATAKANA LETTER TO
  0x30CF,  // KATAKANA LETTER HA
  0x30D2,  // KATAKANA LETTER HI
  0x30D5,  // KATAKANA LETTER HU
  0x30D8,  // KATAKANA LETTER HE
  0x30DB,  // KATAKANA LETTER HO
  0x30CF,  // KATAKANA LETTER HA
  0x30D2,  // KATAKANA LETTER HI
  0x30D5,  // KATAKANA LETTER HU
  0x30D8,  // KATAKANA LETTER HE
  0x30DB,  // KATAKANA LETTER HO
  0x30A6   // KATAKANA LETTER U
];

Photozou.MacUnicode.isNFD = function() {
  var ua = navigator.userAgent;
  if (ua.indexOf("Mac") > -1) {
    // Mac
    if (ua.indexOf("Safari") > -1 || window.opera) {
      // Safari or Opera
      return true;
    }
  }
  return false;
};

Photozou.MacUnicode.normalizeToNFC = function(path) {
  var result = "";
  var pathLength = path.length;
  for (var i = 0; i < pathLength; i++) {
    var c = path.charCodeAt(i);
    if (c == 0x3099 || c == 0x309A) {
      // a voiced sound mark or a semi-voiced sound mark
      if (i == 0) {
        continue;
      }
      var prev = path.charCodeAt(i - 1);
      for (var k in Photozou.MacUnicode.uncombinedKana) {
        if (prev == Photozou.MacUnicode.uncombinedKana[k]) {
          if (result.length > 0) {
            result = result.substr(0, result.length - 1);
          }
          result += String.fromCharCode(Photozou.MacUnicode.combinedKana[k]);
          break;
        }
      }
    } else {
      result += path.charAt(i);
    }
  }
  return result;
};

変換用の文字テーブルがほとんどを占めているため、 実際の処理内容としては大したことをしていません。 一文字ずつ見ていって、必要なら変換しているだけです。 サーバ側で変換する場合も、同様の変換処理をしてあげればよさそうですね。

追記

http://d.hatena.ne.jp/odz/20070904/1188884960
記事に関して上記のようなご指摘をいただきました。 私自身Unicodeに関して詳しいわけではないので、こうしたご指摘はありがたいです。 一部、符号化という語を誤った意味で使っていたので修正しました。 NFC、NFDはUnicodeの正規化形式の種類です。

以下、さらなる補足です。

PHPのmbstringの設定で、 mbstring.http_inputとmbstring.internal_encodingの値が異なり、 mbstring.encoding_translationの値がonになっている場合、 サーバ側で入力パラメータのエンコーディングが自動変換されます。 その場合、NFDで正規化された合成文字は正しく変換されずに 途中で切れてしまう場合があります (そもそもPHPのmbstringモジュールがNFDに対応していないため)。

この問題に対処するためには、 エンコーディングの自動変換をしないようにしてサーバ側で変換処理を行うか、 送信前にJavaScriptで変換してから送信するしかないようです。 ICUを使った正規化ライブラリが存在するようなので、 ICUベースでUnicode対応する予定のPHP6が待ち遠しいところです。

2007年9月 3日

携帯のエラーメールの種類
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

harukiです。

メールを送信するサイトの場合、メールアドレスのクリーニングは定期的に行うべきです。 そのためにはエラーメールを解析しなければなりません。

そこで、エラーメールの種類をまとめてみました。

SMTPエラー

存在しないメールアドレスや、正しい形式でないメールアドレスの場合は、 DoCoMo,au,SoftBankの3キャリアともSMTPエラーになります。

例として、以下の条件でメールを送信したとします。

From: sender@example.com
Return-Path: bounce@example.com
To: アドレス@docomo.ne.jp

MTAにより異なりますので、ここではPostfixを例にします。
Postfixではmultipart/reportのメールがbounce@example.comに届きます
(※ 必要な情報のみに省略しています)

From: MAILER-DAEMON@example.com (Mail Delivery System)
Subject: Undelivered Mail Returned to Sender
To: bounce@example.com
Content-Type: multipart/report; report-type=delivery-status;
	boundary="boudary/example.com"

This is a MIME-encapsulated message.

--boudary/example.com
Content-Description: Notification
Content-Type: text/plain

(メッセージ)

<アドレス@docomo.ne.jp>: host mfsmax.docomo.ne.jp[203.138.180.240] said: 550
    Unknown user アドレス@docomo.ne.jp (in reply to end of DATA command)

--boudary/example.com
Content-Description: Delivery report
Content-Type: message/delivery-status

X-Postfix-Sender: rfc822; sender@example.com

Final-Recipient: rfc822; アドレス@docomo.ne.jp
Action: failed
Status: 5.0.0
Diagnostic-Code: X-Postfix; host mfsmax.docomo.ne.jp[203.138.180.240] said: 550
	Unknown user アドレス@docomo.ne.jp (in reply to end of DATA
	command)

--boudary/example.com
Content-Description: Undelivered Message
Content-Type: message/rfc822

(ヘッダ)
From: sender@example.com
To: アドレス@docomo.ne.jp

(本文)

--boudary/example.com--

Content-Type: message/rfc822のToから、「アドレス@docomo.ne.jp」を取得できます。

特殊なエラーメール

auとSoftBankでは、上記のエラーに加えて、一旦受信されてから送り返されてくるものがあります。 種類は4つあります。
(※ ここでも必要な情報のみに省略しています)

  • 1. multipart/report
  • 2. text/plain
  • 3. multipart/mixed
  • 4. multipart/mixed(自動転送先)

1. multipart/report

Postfixの例で書いたものがサーバから送られてきます。
auとSoftBankにこのタイプがあります。

2. text/plain

拒否している場合のauのエラーメールです。
※ <アドレス@ezweb.ne.jp>の行がない場合もあります。

Content-Type: text/plain; charset=iso-2022-jp
From: Postmaster@ezweb.ne.jp
To: bounce@example.com
Subject: Mail System Error - Returned Mail

次のあて先へのメッセージはエラーのため送信できませんでした。

<アドレス@ezweb.ne.jp>

送信先メールアドレスが見つからないか、
送信先メールサーバの事由により送信できませんでした。
メールアドレスをご確認の上、再送信してください。
Each of the following recipients was rejected by a remote mail server.
---------------------------------------------------
(送信したメールのヘッダ)
From: sender@example.com
To: アドレス@ezweb.ne.jp

(本文)

SoftBankにもこのタイプがあります。

To: bounce@example.com
From: MAILER-DAEMON@softbank.ne.jp
Content-Type: text/plain; charset="iso-2022-jp"
Content-Transfer-Encoding: 7bit
Subject: Non Delivery Notification

送信先エラーにより、配信されませんでした。
To:アドレス@softbank.ne.jp
Date:Sun, 1 Sep 2007 00:00:00 +0900

3. multipart/mixed

auでは送信したメールの内容がないパターンもあります。

To: bounce@example.com
From: Postmaster@ezweb.ne.jp
Content-Type: multipart/mixed; boundary="==boundary"
Subject: Mail System Error - Returned Mail

This message is in MIME format.Since your mail reader does not understand this format, some or all of the message may not be legible

--==boundary
Content-Type: text/plain; charset="ISO-2022-JP"
Content-Transfer-Encoding: 7bit

次のあて先へのメッセージはエラーのため送信できませんでした。

送信先メールアドレスが見つかりませんでした。
メールアドレスをご確認の上、再送信してください。

The user(s) account is disabled.

<アドレス@ezweb.ne.jp>


--==boundary--

4. multipart/mixed(自動転送先)

auでは自動転送先が2つまで設定できます。 自動転送先へメールが届かなかった場合に、以下のメールが送られてくることがあります。

To: bounce@example.com
From: Postmaster@ezweb.ne.jp
Content-Type: multipart/mixed; boundary="==boundary"
Subject: Mail System Error - Returned Mail

This message is in MIME format.Since your mail reader does not understand this format, some or all of the message may not be legible

--==boundary
Content-Type: text/plain; charset="ISO-2022-JP"
Content-Transfer-Encoding: 7bit

次のあて先へのメッセージはエラーのため送信できませんでした。

送信先メールアドレスが見つからないか、
送信先メールサーバの事由により送信できませんでした。
メールアドレスをご確認の上、再送信してください。

Each of the following recipients was rejected by a remote
mail server.

    Recipient: <転送先アドレス>
    >>> RCPT TO:<転送先アドレス>
    <<< 550 Unknown user 転送先アドレス


--==boundary--

4は特殊な状況なので、4以外のエラーメールにはできるだけ対応しておいたほうがいいと思います。

※参考
KDDI au: EZwebへメール送信する際の注意事項 > 技術仕様
http://www.au.kddi.com/notice/manner/jyushin_policy/shiyo.html

追記(09/04 18:15)

はてブのコメントでご指摘いただいたVERPについて、知りませんでしたので試してみました。

Postfix VERP Howto
http://www.postfix.org/VERP_README.html

 Postfix 2.3 and later:

    % sendmail -XV -f owner-listname ....

    % sendmail -XV+= -f owner-listname ....

Postfix 2.2 and earlier (Postfix 2.3 understands the old syntax for backwards compatibility, but will log a warning that reminds you of the new syntax):

    % sendmail -V -f owner-listname ....

    % sendmail -V+= -f owner-listname ....

ということで、PHPのmail関数の場合は、
mail($to, $subject, $message, $additional_headers, '-XV -f bounce');

として、すぐ確認できる種類を試してみました。
(確認できなかった種類 = 2.text/plainのSoftBank)

結果として、確認できた種類ではすべて

To: bounce+アドレス=docomo.ne.jp@example.com
という形式で取得することができました。

貴重な情報をありがとうございます。