Quantcast
Channel: babaの記事一覧|TechRacho by BPS株式会社
Viewing all 101 articles
Browse latest View live

Ruby on Rails 3.2.17, 4.0.3がリリースされました!重要なDoS脆弱性が修正されています

$
0
0

日本時間で2/19の朝、Ruby on Rails 3.2.17, 4.0.3, 4.1.0beta2がリリースされました
今回は、重要なセキュリティFixを含む一斉アップデートです。

なお、4.1についてはすでに4.1.0rc1がリリースされているので、そちらにアップデートしましょう。

今回は3件の修正(3系でのみ発生するものが1件、4系でのみ発生するものが1件、両方で発生するものが1件)が含まれています。
特に最後のDoS脆弱性(CVE-2014-0082)については、Rails 3.2を使用している幅広いサービスに影響があるため、確実に確認・アップデートしておきましょう。

CVE-2014-0080

影響を受けるRailsバージョン: 4.0, 4.1

ActiveRecordでPostgreSQLの配列型を使っている際の、データインジェクション脆弱性です。
レコードを削除したり任意のSQLを実行できる性質のものではありませんが、アプリケーションの動作に影響を及ぼすデータを注入される可能性があります。

PostgreSQLの配列型を使っていない場合は、この脆弱性の影響は無いので心配ありません。
Rails 3.2系もこの影響を受けないので、Rails 4系を使っている場合のみ対策が必要です。

もしアップデートできない場合、個別パッチが提供されているので適用しましょう。

https://groups.google.com/forum/#!topic/rubyonrails-security/Wu96YkTUR6s

個人的にはPostgreSQLをあまり使わないので、詳細確認していません。

CVE-2014-0081

影響を受けるRailsバージョン: 3.2, 4.0, 4.1

number_to_currency, number_to_percentage, number_to_humanのXSS脆弱性です。
formatパラメータがHTMLエスケープされないため、formatをユーザが指定できる環境ではXSSが成立します。

たとえば以下のようなコードです。

number_to_currency 50000, format: params[:format]

Rails 3.2, 4.0, 4.1すべてが影響を受けるので、アップデートするか、個別パッチを当てるか、formatを手動でエスケープしましょう。

個別パッチはこちらです。

https://groups.google.com/forum/#!topic/rubyonrails-security/tfp6gZCtzr4

ちなみに、アップデートしても、localeファイルで指定しているHTMLタグ付きformatはエスケープされないので、一般的には困らないと思います。

# config/locales/en.yml
# このような指定をしていても、アップデート後に特に修正は不要です
en:
  number:
    currency:
      format:
        format: "%n%u"
        unit: "<b>Yen</b>"
        separator: "."
        delimiter: ","
        precision: 0

CVE-2014-0082

影響を受けるRailsバージョン: 3.2

ActionViewでrender :textを使った際のDoS脆弱性です。
以下のようなコードはよくありますが、これでDoS攻撃が成立します。

class HogeController
  def hello
    render text: 'Hello world!'
  end
end

HTTPリクエストのAcceptヘッダに応じてformatsが決定されますが、これは:htmlや:text, :jsのようなsymbolに変換されます。
ここで、Acceptヘッダに長い文字列を少しずつ変えながら大量にリクエストすると、symbolが大量生成されることになります。
symbolはGCで回収されないので、これだけで、OOM Killerが発動するまでメモリ使用量を無限に増大させることができ、DoS攻撃が成立します。

試してみましょう。
まず、わかりやすくするためにシンボル数をレスポンスに含めます。

def hello
  render text: "Hello world!, symbols=#{Symbol.all_symbols.count}"
end

また、メモリ使用量を監視しておきます。

while true; do ps alx | grep rails | grep -v grep | awk '{print $8}'; sleep 2; done

試しにブラウザからアクセスしてみたら、初回はシンボル数15,166、メモリ使用量(RSS) 56,840でした。

次に、Acceptヘッダに長い文字列(とりあえず1KB程度)を入れて、毎回微妙に変えながら1万回ほどアクセスしてみます。
(念のため具体的なコードは避けますが、Rubistなら1行で書けるはずです)

メモリ使用量はこんな感じになりました。
途中GCが走って減る箇所もありますが、トータルでかなり増えてしまっています。

56840
56840
66476
72924
73900
76940
78980
80408
82708
(略)
246264
247560
248532
249304
251288
253240

また、シンボル数も25,164まで増えていました。要するにアクセス回数分増えています。

このペースだと、10分とかからずOOMを発生させることができそうです。

Rails 4.0系ではこの影響を受けないので、Rails 3.2を使っている場合のみ対策が必要です。

個別パッチはこちらです。

https://groups.google.com/forum/#!topic/rubyonrails-security/LMxO_3_eCuc

まとめ

未だRails 3.2は広く使われていることを考えると、(CVE-2013-6414でも似たようなことをやっていた気がしますが)CVE-2014-0082のDoS脆弱性は非常にインパクトがあります。
アップデートは早急に!


情報処理技術者試験を高度全区分制覇したので合格証書を晒してみる

$
0
0

タイトルの通りです。

合格証書

合格証書

高校生の時に吉田先生に勧められて取得を始めた情報処理技術者試験ですが、大学1~3年時代に空白期間があったため10年近くかかりつつも、ようやく高度試験が全部取得できました。こうしてみると、年ごとに紙の色が結構違うことがわかります。

過去に取得したものは以下です。

  • 初級システムアドミニストレータ
  • ソフトウェア開発技術者
  • テクニカルエンジニア(情報セキュリティ)
  • 情報セキュリティアドミニストレータ
  • システムアーキテクト
  • システム監査技術者
  • ネットワークスペシャリスト
  • データベーススペシャリスト
  • ITサービスマネージャ
  • プロジェクトマネージャ
  • ITストラテジスト
  • エンベデッドシステムスペシャリスト

微妙に試験区分が変わっているため厳密に全区分かは微妙ですが、まあだいたい対応しているでしょう。
3個目あたりから単なる惰性なので、資格マニア以外にはあまり意味ないと思います。

まだ基本情報と応用情報を取っていないので、気が向いたらそれも受けてみようと思います。

Railsで大きなファイルを扱う際のポイント

$
0
0

Railsで大きなファイルを扱う際のポイントをまとめてみました。

前提

大きなファイルとは

だいたい100MB~10GBくらいのファイルをダウンロード・アップロードするのを想定することにします。
数MB程度だと、特別な工夫なしでもそれほど問題になりません。10GBを超えてくると、気をつけるべき点が変わってくるかと思います。

以下では主にサンプルとして、1GBのファイル(ISOファイルやZIPファイルなど)を想定します。

環境

以下のような環境を想定します。

  • Railsは4系
  • Nginx + Unicornのスタンダードな構成
  • サーバ1台のシンプルな構成(ロードバランサを使用した複数台構成については、末尾に少し記載しています)

ダウンロード

ファイルのダウンロード

まずは、Railsアプリから大きなファイルを配信するケースを考えましょう。
たとえば、ISOファイルをサーバ内に保存しておいて、認証されたユーザはそれをダウンロードできる、というケースです。

シンプルに実装すると、このような感じになると思います。

# app/controllers/files_controller.rb
class FilesController < ApplicationController
  def show
    send_file Rails.root.join("files/01.iso")
  end
end

しかし、巨大なファイルをRailsが配信するのは、よく考えるとかなり無駄です。
1GBのISOファイルを配信する場合、クライアントとの帯域が50Mbps確保できたとしても、単純計算で160秒間ワーカーが占有されることになります。これでは、Unicornのワーカーを50個用意してもすぐになくなってしまいます。

ファイルを送信するだけなら、Reverse Proxyとして使っているNginxやApacheに処理してもらった方が効率的ですよね。Nginxなら、同時接続数3000くらいあっても余裕でしょう。

方法1: publicに置く

アクセス制限が不要なら、publicなディレクトリにファイルを配置し、そのままNginxやApacheで配信してしまえば良いです。スタイルシートなどのassetsも、この方法で配信します。

方法2: X-Sendfile / X-Accel-Redirectを使う

認証が必要な場合、Rails側からX-Sendfileを指定すると良いでしょう。
方法は、production.rbで以下の行を記述するだけです。デフォルトではコメントアウトで記載されていると思います。

# config/environments/production.rb
# ...
    # Apacheを使う場合
    config.action_dispatch.x_sendfile_header = 'X-Sendfile'

    # Nginxを使う場合
    config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
# ...

これを記載すると、RailsはISOファイル本体を読み取ってResponse Bodyを送信する代わりに、HTTPヘッダに「X-Accel-Redirect: /path/to/files/01.iso」のようなヘッダを付与し、Response Bodyを空にしてレスポンスを終了します。そのヘッダを受け取ったNginxがpublicディレクトリの場合と同じようにファイルを送信するため、Railsのワーカーはすぐに解放されます。

これは内部的にはRack::SendfileというMiddlewareで実現されているので、Sinatraなどで活用することも可能です。

方法3: 別のサーバから配信する

静的ファイルなら、S3など別のサーバに置いておけば、帯域などの心配をせずにすんで楽です。
認証が必要な場合でも、S3のAPIで有効期限付きのURLを発行できるので、問題ないでしょう。
ダウンロード数が多い場合は、検討すべきです。

ストリーミングダウンロード

データがサーバ上の実ファイルではない場合、Railsで順次送信するしかありません。
データの生成に時間がかかる場合(巨大なPDFを作る、別サーバからダウンロードしたデータを順次流すなど)、すべてのデータが準備できる前に送信開始したいものです。

その場合は、response.streamを使用します。
http://api.rubyonrails.org/classes/ActionController/Streaming.html

# app/controllers/download_controller.rb
class DownloadController < ApplicationController
  include ActionController::Live

  def show
    3.times do 
      response.stream.write "Some Data"
      sleep 1
    end
  ensure
    response.stream.close
  end
end

いくつか注意点があります。

なるべくContent-Lengthを指定する

事前にContent-Lengthを指定しておけば、ブラウザ側で「何%ダウンロード中…」とプログレスが表示されます。
もちろん、ヘッダはボディ送信後には送信できないので、最初のデータを送信する前にContent-Lengthを指定する必要があります。

pumaなど対応サーバを使う

UnicornやPumaなどのサーバなら問題ありませんが、Webrickなどを使うとストリーミングが動作しません。
この場合、レスポンスがすべてバッファリングされるため、実質通常レスポンスと同じような挙動になります。

エラー処理を工夫する

データ送信中にエラーが発生しても、すでにレスポンスコードは返しているため、あとからInternal Server Errorに変更するわけにもいきません。
このあたりは設計でカバーする必要があります。

上段でのバッファリングを無効化する

上段のReverse Proxyで、結局全部バッファリングされてしまうようなケースもあります。Nginxなら、X-Accel-Bufferingなどを確認しましょう。

また特定バージョン(忘れました)で、Keep-Aliveとバッファリングの設定によって、Rails側からcloseしてもタイムアウトまでレスポンスを待ち続けてしまうことがありました。なにやら30秒~60秒くらい固まってしまうときは、この辺のバグも疑ってみましょう。

アップロード

次は、大きなファイルをアップロードするケースを考えてみます。
たとえば、ファイルアップローダを作る場合です。ファイルアップロードには、CarrierwaveのGemを使うことを想定します。

クライアントサイドの工夫

クライアント側は、通常のHTMLによる<input type="file">でも良いですが、これだとアップロード中に固まったように見えるため(最近のブラウザではステータスバーに進捗が出ることはありますが)、UXとしてはいまいちです。

jQuery File Uploadなどを使えば、視覚的なプログレスバーを表示できるのでおすすめです。なお、今回はクライアント側についての詳細は省きます。

何も工夫しない場合

単純に実装すると、このような流れになります。

upload1

青い部分がデータ転送やファイルコピー・移動で、上下の太さはかかる時間の大小を表すつもりで見てください。1GBのファイルをアップロードする場合では、以下のような点で時間がかかります。
ファイルアップロードに関係ない各種処理やオーバーヘッドは除外しています。

1. ブラウザからNginxへHTTP送信
通常、一番時間がかかる部分です。帯域に依存します。たとえば実効速度で50Mbpsの帯域なら、160秒程度かかります。
2. NginxからRailsにHTTP送信
Nginxがバッファリングしたデータを、Railsアプリに送信します。Reverse ProxyであるNginxとRailsアプリは、通常ローカルネットワークまたは同一マシンにあるため、帯域は十分でしょう。仮に800Mbpsとして、10秒程度かかります。
Railsアプリが受信したデータは、Rackのレイヤーで一時ファイルに保存され、Rack::Multipart::UploadedFileとしてControllerから参照できます。
3. Carrierwaveのcache dirにコピー
Carrierwaveは、Rack::Multipart::UploadedFileを一時ディレクトリにコピーします。これはローカルストレージ内でのコピーなので比較的高速です。仮に200MB/秒として、5秒程度かかります。
4. Carrierwaveの途中処理
version指定をした場合など、ここで加工処理(たとえばImageMagickで画像縮小)が実施されます。今回はシンプルなアップローダなので、何もしません。
5. Carrierwaveのstore dirにコピー
モデルをsaveするタイミングで、cache dirに保存されたファイルを、保存ディレクトリにコピーします。これはローカルストレージ内でのコピーなので比較的高速です。仮に200MB/秒として、5秒程度かかります。

これでようやく、クライアントに「アップロード完了」のレスポンスを送信できます。ブラウザから見た時間を合計すると、

  • (A) リクエスト送信開始~送信完了するまで: 160秒
  • (B) リクエスト送信完了~レスポンス受信開始まで:20秒

となります。

ここで、(A)はある程度仕方ないのですが、(B)に20秒かかっていることが問題です。これだけ時間がかかると、ファイルサイズ次第でクライアント側がタイムアウトしてしまう恐れがあります。

サーバ側での対策

そこで、大きなファイルを扱う場合には、以下のような対策を両方実施するのが有効です。

  • リバースプロキシ(Nginx)とRailsを同一マシンで動作させ、Nginx upload moduleを使う
  • Carrierwaveでmove_to_cache, move_to_storeをtrueにする

Nginx upload moduleを使う

「2. NginxからRailsにHTTP送信」で、NginxからRailsに巨大なファイルをHTTP経由でRackに渡している部分は、大きな無駄です。そこで、Nginxが直接一時を生成してしまうのがNginx upload moduleです。

http://wiki.nginx.org/HttpUploadModule

Nginxの設定方法は概ね以下のような感じです。

# Ubuntuでは以下のコマンドでupload moduleをインストールできる
apt-get install nginx-extras
# /etc/nginx/nginx.conf
# nginxの実行ユーザとRailsの実行ユーザを同一にする。
# これをしないと、CarrierWaveがchmodするときにエラーになる。
user my_app_user;
# /etc/nginx/sites-available/my_app.conf
upstream my_app {
  server 127.0.0.1:8080;
}

server {
  listen 80;
  root /home/my_app/rails/current/public;
  client_max_body_size 5g;

  location / {
    try_files $uri @application;
  }

  location ~ ^/upload$ {
    if ($request_method = POST) {
      upload_pass @rails;
      upload_store /tmp;
      upload_store_access user:rw group:rw all:rw;
      upload_set_form_field "$upload_field_name[filename]"   "$upload_file_name";
      upload_set_form_field "$upload_field_name[type]"       "$upload_content_type";
      upload_set_form_field "$upload_field_name[tempfile]"   "$upload_tmp_path";
      upload_aggregate_form_field "$upload_field_name[md5]"  "$upload_file_md5";
      upload_aggregate_form_field "$upload_field_name[size]" "$upload_file_size";
      upload_pass_form_field ".*";
    }
  }

  location @rails {
    proxy_redirect off;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_buffering off;
    proxy_pass http://my_app;
  }
}

このモジュールを利用すると、Nginxがファイルアップロードのリクエストを受け付けた際、それをProxy先に転送する代わりに指定ディレクトリに保存するようになります。
RackにはNginxが作成した一時ファイルのパスだけが渡されるので、「2. NginxからRailsにHTTP送信」で10秒かかっていた処理は一瞬で完了します。ただしそのままだとRack::Multipart::UploadedFileが生成されないため、Railsアプリ側で多少処理が必要です。

# app/controllers/upload_controller.rb
class UploadController
  # POST /upload
  def upload
    file = ensure_uploaded_file(params[:file])
    # ...
  end

  private
  # Nginx upload module経由で受信した"tempfile"パラメータを参照し、
  # 手動でActionDispatch::Http::UploadedFileを作成する。
  #
  # Nginx設定をしなくても低速ながら動作するように、ifチェックを入れておく
  def ensure_uploaded_file(file_or_hash)
    if file_or_hash.is_a?(Hash) && file_or_hash[:tempfile]
      # Nginx upload module経由の場合
      file_or_hash[:tempfile] = File.new(file_or_hash[:tempfile])
      ActionDispatch::Http::UploadedFile.new(file_or_hash)
    else
      # 通常の場合
      file_or_hash
    end
  end
end

Carrierwaveでmove_to_cache, move_to_storeをtrueにする

Carrierwaveでは、デフォルトでファイルを2回コピーします。これは、画像のサイズ変換などをしやすいほか、Permission問題が起きづらいメリットがあるのですが、大きなファイルにおいては非常に大きな時間の無駄です。

今回のようなシンプルなアップローダでは、コピーの必要はないので、mvしてしまいましょう。これは、オプションを指定するだけで簡単にできます。
もちろん、Nginx upload moduleで指定するupload_storeと、最終的な保存先であるstore_dirは、同じパーティションにする必要があります。

# app/uploaders/files_uploader.rb
class FileUploader < CarrierWave::Uploader::Base
  storage :file

  def move_to_cache
    true
  end

  def move_to_store
    true
  end
end

これにより、Nginxが作成した一時ファイルが、そのまま最終的なstore directoryに移動されることになります。移動は一瞬なので、「3. Carrierwaveのcache dirにコピー」「5. Carrierwaveのstore dirにコピー」がほぼゼロになることになります。

合計すると以下のようになります。

  • (A) リクエスト送信開始~送信完了するまで: 160秒
  • (B) リクエスト送信完了~レスポンス受信開始まで:ほぼ0秒

upload2

通常のアクションと同じ反応速度を実現できるようになりました。めでたしめでたし。

ところで

お気づきかと思いますが、これだけやってようやくPHPでmove_uploaded_file使うのと同じになっただけです。特別早くなったのではなく、普通になったのです。

多段Proxyの場合どうするのか

  • 負荷分散のため、Reverse ProxyとRailsアプリを別マシンにしたい
  • グローバルIPの関係で多段Reverse Proxyしたい

このような場合は、前段にNginxを使うのはあきらめましょう

現バージョンのNginxでは、ProxyにUploadする際にはリクエストをすべてBufferingする必要があります。
そのため、多段ではどうしても上記「2. NginxからRailsにHTTP送信」に相当する時間がかかってしまいます。

upload3

対策としては以下のようなものが考えられます。

  • もうすぐ実装されるらしいので、それを待つ
  • バッファリングせずにアップロードできるReverse Proxyを使う
    • Tengine (Nginxのfork) だと実現できるらしい
    • 多くのロードバランサーもおそらく大丈夫でしょう

まとめ

本記事の内容を活用すれば、RailsでもPHPを普通に使うのと同じ程度のことはできるようになると思います。小規模チーム用のアップローダを作る程度なら、この程度で十分でしょう。
小規模なら本当にRailsで実装する必要があるのかは疑問ですが、使い慣れたツールでのやり方を把握するのは無駄ではないでしょう。

アクセスの多い公開用サイトでは、先に帯域の問題と向き合わなければならず、アプリだけでなくインフラやサービスレイヤーでの工夫が必須になります。そのあたりは今回触れていませんが、おもしろいテーマなので機会があれば検証してみたいですね。

1年近く前に小規模なファイルアップローダを実装した際の記憶で書いているため、誤りがあればご指摘いただけば幸いです。

【Googleで】シリコンバレーでW3C TPAC 2014 Santa Claraに参加してきた【遊んできた】

$
0
0

baba & skkで、シリコンバレーに旅行出張に行ってきました!

TPAC 2014

何しに行ったのか

2014年10月26日~31日、Santa Claraで開催されたTPAC 2014に参加してきました。
BPSは最近W3Cのメンバーになったばかりなので、これが初参加になります。

超縦書シリーズを通して日本語組版のCSS表現に関わっているため、今回は主にCSSWG (CSS Working Group) へ参加しました。
CIMG0579

TPACとは?

W3C

W3C (World Wide Web Consortium) は、言わずと知れたWeb関連の標準化団体です。HTML, CSS, XML, SVGなど数々の技術の標準化作業を推進しています。1994年にTim Berners-Lee氏が設立し、今年で20周年になります。

アメリカ(MIT)、ヨーロッパ(EPCIM)、日本(慶應義塾大学)の3カ所がホストとなり共同運営されています。
SFCに在籍していた頃は、「なんかW3Cの支部があるんだろう」くらいで、その偉大さを理解していませんでした;;

TPAC

TPACは、簡単に言えばW3C最大の会合です。

W3Cの主要な活動は、主にWorking Group単位で行われています。
たとえばCSSWGでは、都度MLで議論を進め、週1回程度の電話ミーティングが開催されているようです。そのほかに年3回程度(まだ詳細知りません)のF2F(Face to Face Metting)があります。

lunch-discussion

そして、全Working Groupが一同に集まり、全体を通した議論や交流を行うのがTPACです。
今年は20周年なので、いつもより豪華!のはず!初参加なのでよく分かりません。

出張概要

メンバー

榊原、馬場の2名で参加しました。

榊原
BPSのAC(Advisory Committee)。BPSを代表して参加。
馬場
技術担当。日本から出るのは初めて。慌ててパスポートを取得。

どちらもTPACは初めてなので、お世話になっているViblioStyle村上氏と合流し、色々案内して頂きました。

場所

Santa Clara Marriottで開催されました。

Santa Claraはカリフォルニア州の都市で、いわゆるシリコンバレーの中心部です。
すぐそばにはIntelミュージアムがあるほか、車で数十分の範囲内にGoogle, Apple, Yahoo, Adobe, Intelなど世界のIT巨人たちが集結しています。Broadcom, EMCなどハードウェア系企業も目立ちました。

california シリコンバレーの場所

日程

TPACは月曜~金曜まで5日間開催されましたが、今回は最初の3日間のみ参加しました。

10/26 (Sun)
  • 16:50 (JST) 成田空港発
  • 09:44 (PST) シアトル・タコマ国際空港着
  • 14:03 (PST) サンフランシスコ国際空港着
  • CSSWGや他主要メンバーとメキシコ料理ディナー
10/27 (Mon)
  • CSSWG出席
  • 村上氏や日本人コミュニティとの中華料理ディナー
10/28 (Tue)
  • CSSWG出席
  • 一緒に行動させて頂いた方や現地のご友人とディナー
10/29 (Wed)
  • Google本社見学
  • W3C 20周年記念式典
  • Gala Dinner
10/30 (Thu)
  • 09:15 (PST) サンフランシスコ国際空港発
  • 13:03 (PST) シアトル・タコマ国際空港発
10/31 (Fri)
  • 16:00 (JST) 成田空港着

Marriottホテル

周辺

Google本社

水曜日に、知り合いのGoogle社員の方に案内して頂きました。

CIMG0625 CIMG0644 CIMG0641

一番右のディスプレイがたくさんある写真は、Google Earthが映っています。専用コントローラで自在に移動・ズームインできるだけでなく、視点を傾けて横から見ることもできます。BPSがある新宿付近を俯瞰しましたが、まるで鳥になったような感覚が味わえます。
20%ルールで始めるプロジェクトでも、数万台規模のサーバを使いまくれる。この辺はさすが天下のGoogle様ですね。EC2の一番高いインスタンスを1時間だけ起動して喜んでいる僕とは違うのです。うらやましい。

もちろん、現在世界中で検索されている箇所を視覚化するディスプレイ、巨大なNexus Sなど、有名なオブジェもありました。無料のランチも頂きました!

CIMG0633CIMG0622CIMG0638

世界中でここにしかないお土産屋さんでは、Googleグッズがたくさん。今回はカラフルなトートバッグなどを買いました。大きい旅行ケースがあれば、たくさん買い込んだのに。

スタンフォード大学

一瞬迷い込みました。
世界の名門大学、さすがの雰囲気と圧倒的な広さ!周辺の街まで輝くようだ!

スタンフォード大学のキャンパスは、新宿区と渋谷区を合わせたくらいの広さがあるそうです

食事

アメリカの食事はまずいだろうと思って覚悟していたのですが、意外といけるものが色々ありました。
高級なところなら、おいしい食べ物もあるのですね。食費は高かった。

CIMG0588

Computer History Museum, Intel Museum

本当は行きたかったのですが、時間が無く今回は行けませんでした。

W3C 20周年記念式典

今年はWebの25周年、W3Cの20周年です。神のごとき存在であるTim Berners-Lee氏を直に拝むことができました。

村井先生もパネルディスカッションに参加していて、完全に議論をリードしている雰囲気でした。

CIMG0695 CIMG0704 CIMG0700

Tim Berners-Leeの英語はとにかく速いです。なにやらすごそうだ、という感じで、何を言っているのかまったく聞き取れませんでした…

CSSWGのトピック

本題です。これがないと単なる観光ですね。

雰囲気

事前にAgenda案をwikiに書き込んでおき、それに応じて順次進んでいきます。
具体的な議題は、まったく新規のproposalもありますが、www-styleで議論していたものが持ち込まれることも多いようです。

IMG_1336

議論の内容

全部を書き出すのは無理なので、印象に残った点など一部抜粋してみます。

::selection

テキストなどを選択したとき、PCのブラウザだと一般的に青くなりますが、この部分を指定するセレクタとその挙動についてです。
たとえば、選択部分の色を変えたい(スマホのブラウザだと水色などが使われています)というのが最も一般的な用途でしょう。この場合、shadowやtransformとの兼ね合いをどうするのか、といったあたりが主なポイントになります。

そのほかroleを一括して指定するセレクタなども提案されていました。Selectors Level 4はもうすぐCRにするからLevel 5に載せようぜ、言う声もありましたが、Level 3の時点でモジュールとレベルという構造が破綻し始めているので、ちょっと突っ走りすぎじゃないかと思ったり思わなかったり。

Indefinite margins and padding

css-sizingのblock intrinsic sizeの中に以下の記述があります。

If the computed inline-size of a block-level box is min-content, max-content, or a definite size, its min-content inline-size contribution is that size plus any inline-axis margin, border, and padding. Otherwise, if the computed inline-size of the block is fit-content, auto, or fill, its min-content inline-size contribution is its min-content inline-size plus any inline-axis margin, border, and padding.

このとき、仮にmargin/paddingがindefinite(%指定)だった場合はどうするのか、という議論がありました。
0と解釈するのが妥当そうだが、intrinsic sizeの場合のみ特別扱いするのは不自然なので、layoutの中でも統一的に扱いたい。floatやshrink-to-fitの挙動との整合性はどうか?特にこの挙動は既存のWebとの互換性に注意が必要な箇所で、Orthogonal Flowの挙動を含めるとブラウザ間の動作もばらつきがあり、バランスの良い解を見つけるのは容易ではありません。結局、議論は持ち越しになりました。

MediaQueries

Media Queriesは、CSSを適用する条件を指定するものです。代表的なものに、@media printの指定があります。
レスポンシブデザインも、Media Queriesを使用して実現されています。たとえばbootstrapのCSSを見ると、@media (min-width: 768px)のような記述がたくさんありますね。

Media QueriesはLevel 3がRecommendationになっているので、主にLevel 4に向けた議論になります。

たとえば、Windowsでは「簡単操作センター」でハイコントラストモードをONにできますが、Internet Explorerでは-ms-high-contrastというクエリでこれを判定できます。これを標準化するか?するとしたら文言はどうするのか?ハイコントラストモードは主に色弱の方が使うので、判別できるとプライバシーの面で問題があるのではないか?などといった議論がされました。

overflow-block, overflow-inlineという用語についてもいくつかの意見がありました。このクエリは、Webと電子書籍で共通ソースを使う場合などで重要になるため、ウォッチしておきたいところです。

現在、Media Queries Level 4はFPWD(First Public Working Draft)となっています。

Justification Rule

日本語と韓国語が混ざった場合、Justification Opportunityはどこにあるのか?autoの場合、言語の判別はどうするのか?といった議論がありました。
言語ごとに、デフォルトのJustification Ruleは異なります。英語なら単語間で、日本語なら文字間で調整します(厳密には、日本語組版なら約物周りの調整ルールなどもありますが、それは現在のCSSのレベルを超えます)。このとき、言語をどのように判定すべきか?要素にlang属性があれば分かりやすいですが、多くのコンテンツは多言語が混ざる場合にいちいちlang属性を付けていません。「lang属性 → Content-Language HTTPヘッダ → OSロケール → コンテンツスニッフィング → charsetから推測 の順でフォールバック」など、色々な案が出ましたが、結局詳細は規定せずNOTEまたはWikiで記載、ということで落ち着きました。
Sniffingはトラブルが起きやすく、HTTPヘッダはローカル保存した際に挙動が変わるためセキュリティリスクを含めた混乱が起きやすいため、十分な議論を経ずに仕様化してしまうことは危険です。今回の結論は妥当だと思います。

反省、感想

良かった点

多くの方にとても良くして頂き、普段ならとてもお会いできないような方々とも交流ができました。少々おなかいっぱい過ぎるくらいです。
WGでの発言は少ししか出来ませんでしたが、W3CおよびCSSWGの会議体・コミュニケーションパスが把握でき、参加していく道が明らかになったのも大きな収穫です。
10426848_832116523507765_3761679123511680607_n

失敗した点

  • 木曜日に日本コミュニティのディナー会があることを知ったが、既に飛行機の予約は確定していて参加できなかった。
  • www-style MLの存在すら知らない知識不足で、初動が遅れた。
  • お土産のワインを空港のセキュリティチェックで没収された。
  • 撮影した写真が観光っぽいものばかりで、いかに有意義なカンファレンスだったかが伝わらないと怒られた。
  • Baba MUST study English.

今後について

直近では、まずはCSSWGを中心に会議への発言を増やしつつ、縦書きや日本語組版に関する部分の実装をblinkへコミットしていくことを目指しています。
W3Cメンバーとして少しずつ貢献していきたいと思っています。

榊原が写っています

榊原が写っています

続・EPUBでのtext-align:justifyと-epub-text-align-lastを知る

$
0
0

以前書いたtext-align-lastについて、その後Working Draftの更新などがありましたので、続きです。

現在のステータス

text-align-lastについて記載されているCSS Text Module Level 3は、2014/11/14現在、Last Call Working Draftのステータスです。

CSS Working Groupとしては概ね仕様案が固まり、外部からのフィードバックを受け付けるステータスと思って良いでしょう。

以前の状況

以前参照した20121113版では、text-align-lastについて、以下のようになっていました。

  • start, end, left, right, center, justifyの場合: 最終行をその位置にそろえる
  • autoの場合:
    • text-align:justifyの場合:
      • text-justify:distributeの場合: 最終行もjustifyにする
      • text-justify:distribute以外の場合: 最終行はstartにそろえる
    • text-align:justify以外の場合: 最終行もtext-alignに従う
  • 1行しかない場合は、text-align:start endの場合を除き、text-alignよりtext-align-lastが優先される

以上のルールにより、以下のようなHTMLでは、pのサイズをよほど小さくしない限り、左寄せになる仕様でした。

[CODE-1]

HTML:
<p>This is test.</p>

CSS:
p {
  text-align: center;
  text-align-last: left;
}

従来の問題点

整合性より、直感的でない、実用上使いづらいという点が問題だったのでしょう。

そもそもtext-align-lastは、「両端揃えしたいが、最終行の余った部分まで均等割り付けするのはおかしい。最終行は先頭揃えにすべきだ」という常識的なルールを設定するために用意されたはずです。ですので、初期値はautoで、この挙動としては「justify時も最終行はstart寄せ」になっています。

この視点で見ると、1行の時にtext-align-lastがtext-alignより優先する理由もわかります。つまり、短い行を不自然な均等割り付けさせないためのプロパティなのだから、短い1行にも適用されなくてはいけないわけです。

しかし、[CODE-1]のような記述でセンタリングしないのは、あまり直感的ではありません。プロパティ名から受けるイメージとしては、「1行しかないのだし関係ないだろう」「pタグの中に画像が1つあるだけなのだから、関係ないだろう」と感じるのが普通だと思います。

だいたい、text-align-lastをjustify以外で使うケースがあるのか?いったい誰がうれしいのか?混乱を招くだけではないのか?当然そのような意見もあり、この仕様は何回か更新されています。
過去の議論の例

変更点20131010

Working Draftは随時アップデートがされていますが、text-align-lastに関しては、20131010版にて、以下の変更が加えられました。

Changed ‘text-align-last’ to depend on ‘text-align: justify’.

具体的には以下の下線部分が追加されています。

This property describes how the last line of a block or a line right before a forced line break is aligned when ‘text-align’ is ‘justify’. If a line is also the first line of the block or the first line after a forced line break, then, unless ‘text-align’ assigns an explicit first line alignment (via ‘start end’), ‘text-align-last’ takes precedence over ‘text-align’.

これにより、text-align-lastプロパティは、text-align:justifyの時のみ作用するように変更されました。よって、[CODE-1]の場合、センタリングすることになります。

unless ‘text-align’ assigns an explicit first line alignment (via ‘start end’),の部分が残っていて「?」となりますが、おそらく消し忘れでしょう。

これによって、かなり直感的な挙動になりました。

  • デフォルトでは、text-alignは1行でも複数行でもそのまま効く。ただし、justifyのみ、短い行はstart寄せになる。
  • text-align-lastを指定すると、justifyの場合の挙動のみ変更できる。設定しても、text-align:centerの場合などは作用しない。

欠点としては、機能・自由度が減っている(justify以外の場合に最終行の位置を変更したいケースは、本当に絶対無いのか?)点が挙げられるでしょう。仕様を制作事情に寄せすぎで、プロパティレベルではもう少しプリミティブな方が望ましい気もします。

変更点Editor’s Draft

Working Draft内にも注記があるとおり、本記事作成時点でのEditor’s Draftでは、以下の変更がされています。

  • 20131010の変更はキャンセル
  • text-alignはtext-align-allとtext-align-lastを一度に指定するshorthandプロパティとなる
  • text-align-allは従来のtext-alignに概ね相当する
  • text-alignにjustify-all以外を指定すると、text-align-lastはautoにリセットされる

非常におもしろい変更です。

これにより、20131010版と同様の直感的な使い勝手を実現しつつ、明示的に指定することでjustify以外の場合の最終行位置を変更することも可能になります。
仕様のシンプルさと利用頻度を考えれば、justify-allが必要かどうかはやや疑問です。

なお、「text-align-default」のようなshorthandプロパティを新設するのではなく既存のtext-alignを置き換えている理由は、単純に互換性のためです。
text-alignプロパティはCSSが登場したころからあるプロパティです。既存コンテンツの互換性はもちろんですが、急に「これからはtext-alignではなくtext-align-defaultを使ってね」と言ったら、世界中にいる大量のHTML/CSSコーダーが発狂してしまいます。

懸念点としては、プロパティが増えている点が挙げられるでしょう。特にtext-alignがshorthandに変更されているため、JavaScriptから値を取得するようなケースでどのような副作用が生じるか、慎重な検討が必要と思われます。

EPUBの場合

ここで、例によってEPUBの場合の特例が生じます。

EPUB3では、「基本的にCSS2.1を参照する」「text-align-lastなど一部のプロパティのみCSS3の各モジュールを参照する」ことになっています(ちなみにsyntaxはEPUB3制定時点のものを、semanticsは最新のものを参照しています)。

仮に現在のEditor’s DraftがそのままWorking Draftに反映されるとどうなるかというと、

  • text-align-lastは最新のWorking Draftを参照するため、text-align:justifyに依存することはない
  • text-alignはCSS2.1のものを参照するため、shorthandではなく、text-align-lastを初期化することもない

ということで、20131110より前の挙動に戻ることになります。

これはEPUBがCSS3過渡期に制定された都合上、中途半端に参照していることで生じた矛盾と言えるでしょう。

手っ取り早い解決方法としては、EPUBの仕様をアップデートしてCSS3Textのtext-alignとtext-align-allを参照させてしまうのが良さそうですが、影響範囲に注意が必要なうえあまりエレガントではありませんね。IDPF側の議論はあまりウォッチできていないのですが、何か良い案はあるのでしょうか。

まとめ

このように、justifyとtext-align-lastに関しては、まだCSSWGでも明確な答えが出ていない状況です。
コンテンツ制作者側からのフィードバックも募集しているようですので、気づいた点があればwww-styleなどに送ってみると良いかもしれません。

Mac版Qt Quick2でESCキーをハンドリング

$
0
0

Qt QuickでQMLなアプリを書いているとき、エスケープキーをハンドリングしたくなりました。

# qml/myapp.qml

Keys.onEscapePressed: console.log("ESC");

Windows版ならこれで良いのですが、Mac版だとイベントが発生しません。Shift + ESCを押すとなにやら反応します。
Macは使わないのでよく知らないのですが、ESCをハンドリングするのは一般的ではないのでしょうか?

ひとまずこれでは困るので、イベントループから取ってきてしまいました。

# my_application.h
class my_application : public Application {
    Q_OBJECT
public:
    explicit my_application(int &argc, char **argv);

protected:
    virtual bool eventFilter(QObject *obj, QEvent *event);
};


# my_application.cpp
bool my_application::eventFilter(QObject *obj, QEvent *event) {
    if (event->type() == QEvent::KeyRelease) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *> (event);
        if (keyEvent->key() == Qt::Key_Escape) {
            // Some process.
            qDebug("ESC");
            return true;
        }
    }
    return QObject::eventFilter(obj, event);
}

これで無事ハンドリングできました。

※Qt 5.2.1を使い、Qt Quick Controlsなアプリケーションです。

SFC Open Research Forum (ORF) 2014に出展します

$
0
0

直前のお知らせですが、明日11月21日(金)~22日(土)に六本木ミッドタウンで開催されるSFC Open Research Forumに出展させて頂くことになりました。

orf

W3Cブースの片隅で、「Webと電子書籍」をテーマに展示します。六本木にお越しの際には、是非お立ち寄りください。

Qt Quick2でMouseAreaの外のイベントもハンドリング

$
0
0

Qt QuickでQMLなアプリを書いているとき、MouseAreaだけではマウスイベントを満足に捕捉できなくなりました。

前回の記事とほとんどかぶってます。

たとえば、ウィンドウの端にマウスが到達したらイベントを発生させたいけれど、その下にボタンがあるのでイベントを横取りして欲しくない、などです。

# my_application.h
class my_application : public Application {
    Q_OBJECT
public:
    explicit my_application(int &argc, char **argv);

protected:
    virtual bool eventFilter(QObject *obj, QEvent *event);
};


# my_application.cpp
bool my_application::eventFilter(QObject *obj, QEvent *event) {
    if (event->type() == QEvent::MouseMove) {
        QMouseEvent *mouseEvent = static_cast<QMouseEvent *> (event);
        QPointF pos = mouseEvent->windowPos();

        // qmlRegisterType + setContextPropertyを使用してC++とQMLを
        // 連動させれば、QMLにイベントを渡せる。

        /* Fall through */
    }
    return QObject::eventFilter(obj, event);
}

これで無事に全体のマウスイベントをとれました。

EventFilterは協力なので、あまりやると「何でQML使ってるんだっけ?」となるのでほどほどに。


SFC Open Research Forum (ORF) 2014に出展しました

$
0
0

既報の通り、11月22日~23日に六本木ミッドタウンで開催されたSFC Open Research Forumに出展してきました。

▼W3Cブース内、BPS展示エリアです。
DSC_0187

▼近くのブース。
DSC_0204

▼ちゃんと仕事したという証拠写真。
DSC_0190

▼W3Cブースでは、「Webと音楽」がメインテーマです。村井先生によるギター演奏は大盛況でした。
DSC_0197

▼今回BPSは、超縦書とW3C CSSWGの関連、今後の展望について展示・紹介させて頂きました。
DSC_0199

▼今回弊社はスポンサーではありません。ごめんなさい。
DSC_0210

▼骨組みだけになるところまで、業者無しでほとんど学生が片付けていたのですが、なかなか手際が良くて驚きました。
DSC_0212

普段会えない方やしばらくお会いしていない方に会えたり、SFCの後輩と楽しくお話したり、予想よりだいぶ充実した2日間を過ごせました。

しかし、10時間+8時間立ちっぱなしは、デスクワーク派には拷問です。後遺症の体調不良がまだ抜けません。

フレッツ光メンバーズクラブのポイントを交換するために契約回線を確認する方法メモ

$
0
0

いよいよ光コラボモデルが始まってきました。

自宅はBフレッツをアップグレードしたフレッツ光ネクストファミリータイプで、つまり100Mbpsなので、これを機にそろそろギガっぽくしたくなります。

転用する場合、フレッツ光はいったん解約となるので、フレッツ光メンバーズクラブのポイントを確実に使い切っておきましょう。月額使用料に充当しても良いし、ギフトと交換もできます。
転用しない方も、3月くらいで期限が来るかもしれないので、確認しておいた方が良いと思います。

契約回線の確認

ところが、頑張ってIDとパスワードを引っ張り出してきて、実は初めてじゃないかというレベルでログインして「ポイントを使う」を選ぶと、「契約回線の確認」をしろと言われてしまいます。

keiyaku

https://members-club.flets.com/pub/pages/auth/

クリックしたら、「確認できませんでした」と言われてしまうではありませんか。ちゃんと契約している回線から接続しているのに。

原因

少し確認したところ、この確認は「サービス情報サイト」に接続できないと認証されないようです。
「サービス情報サイト」は、要するに「フレッツ・スクエア」の名前が変わったものですね。フレッツ・スクエア時代は面白がってよく接続していましたが、最近は全く見ていないため名前が変わったのも知りませんでした。

「サービス情報サイト」への接続には、当然ながらPPPoEセッションをもう1つ張る必要があります。また、DNSも設定してやる必要があります。
メインISPにつなぐ設定しかしていなかったので、接続できないのは当然でした。

対策

PPPoEの複数接続は環境によって違いますが、とりあえず我が家で使っているRTX1000を例にしてみます。
こだわる方はシスコの良いやつをラックに付けてISPも複数契約していると思いますが、一般家庭ではRTXシリーズあたりが妥当でしょう。

IPv6でやっても良かったのですが、手っ取り早く家族で楽しくサービス情報サイトに接続するのが目的ですので、今回はv4でいきます。

追加前は、ごく一般的な設定として、LAN2でPPPoEをISPに張り、LAN1をメインの家庭内LAN、LAN3は未使用です。

# サービス情報サイト用にPPPoE設定を追加
pp select 2
 pp name "Flets Service" # 名前はなんでもいい
 pp always-on on
 pppoe use lan2
 pppoe auto connect on
 pppoe auto disconnect on
 pp auth accept pap chap
 # ユーザIDとパスワードは以下に書いてあります
 # https://flets.com/next/square/connectv4/win/
 pp auth myname guest@v4flets-east.jp guest
 ppp lcp mru on 1454
 ppp ipcp ipaddress on
 ip pp mtu 1454
 ip pp nat descriptor 2
 pp enable 2

# NATは使い回しても良いけど一応追加しておきます
nat descriptor type 2 masquerade
nat descriptor address outer 2 ipcp
nat descriptor address inner 2 auto
nat descriptor masquerade incoming 2 reject

# サービス情報サイト関連の接続はpp2を使うようにします
# ルーティング情報は以下にあります
# https://flets.com/square/routing.html
#
# ひかり電話使わないから最後の1個は端折った
ip route default gateway pp 1
ip route 123.107.190.0/24 gateway pp 2
ip route 220.210.194.0/25 gateway pp 2

# DNS設定します
# v4flets-east.jpのみサービス情報サイトのDNSにすれば十分でしょう
# https://flets.com/customer/next/square/faq/faq_dns.html
#
# ppp ipcp msext on しておけば手動でIP打たなくても良かった疑惑
dns server pp 1
dns server select 1 123.107.190.5 any v4flets-east.jp
pptp service on

確認

Webブラウザでhttp://v4flets-east.jp/を開いて表示されればOKです。
PPPoE接続できているのに表示されない場合は、DNSルックアップができているか、ルーティングテーブルが正しいかなどを確認しましょう。

このサイトが表示できている状態なら、「契約回線の確認」も問題なく通過すると思います。

まとめ

以上、フレッツ光メンバーズクラブのポイントを交換するために必要な手順でした。こんなことをやるのに1時間くらいかかってしまった。

最近だとNTTからWiFiルーター付きのゲートウェイが貸与されるので、ひょっとしたらそれを使ってる場合はすんなり通過するのかもしれませんが、Bフレッツ時代から使っているような人はGE-PON-ONUが置いてあると思うので、このような手間がかかります。このようなこともありますので、遠隔の実家にRTXを設置してあげる場合などは、リモートで設定できる環境を準備しておくことも必要ですね。

無事ポイントが交換できたので、ルータはRTX1200かRTX810あたりにして、ギガに移行しようと思います。

JavaScriptでElement.styleがnullになって焦った

$
0
0

JavaScriptで要素のstyleがnullになってしまう事態が発生しました。

// 正常
document.getElementsByTagName('p')[0].style
=> CSSStyleDeclaration {alignContent: "",…}

// あれ?
document.createElement('p').style
=> null

結論としては、Content-Typeがうっかりtext/xmlになっていたのが原因でした。

試してみましょう。

<?php
header('Content-Type: text/xml');
echo '<?xml version="1.0" encoding="UTF-8"?>';
?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>sample</title></head>
<body><p>Hello</p></body>
</html>

コンソールからdocument.createElement('p').styleを入力すると、以下のようになりました。
Chrome以外では値が取れていません。

  • Chrome 46: CSSStyleDeclaration
  • IE11: undefined
  • Firefox 44: undefined
  • Safari 8: null

styleはDOM API CoreではなくCSS用拡張なので、XMLとして表示している場合には存在しなくて正常だと思うのですが、すべてのブラウザで表示上はHTMLモードであること、静的に存在する要素ではstyleが存在する(CSSStyleDeclarationが取得できる)ことから、気づくのに時間がかかった…

表示上はHTMLモードでAPI上はXMLモードみたいなこの挙動は仕様上どうなのでしょう、詳細調べてくれる親切な人がいたら歓迎です。

なお、HTMLの内容がXHTMLではない場合は、XMLエラーになります。xmlnsを指定しない場合は、XMLとして表示されます。

ちなみになんでtext/xmlになってしまったかというと、CocoaHTTPServerで何も設定しないでXHTMLを返したらそうなっていた、というのが原因です。

TPAC 2015 札幌に参加しています

$
0
0

昨年に引き続き、TPAC@札幌に参加しています。今年は日本開催ということで、前回より少し多めの人数で北海道に来ています。

CIMG1097

babaは主にCSS WGとDPUB IGに参加しています。現地からあまり早くもない速報をお届けします。

CIMG1107

TPAC 2015とは

TPACはW3C最大のカンファレンスで、1年に1回開催されます。昨年はアメリカSanta Claraで開催されました。

今年は2015/10/26(月) ~ 30(金)の5日間、札幌コンベンションセンターで開催されています。

なかなか斬新な会議も開催されているようです。

なかなか斬新な会議も開催されているようです。

サイネージは日本企業が準備しているようです。弊社も一部関わったらしい。

サイネージは日本企業が準備しているようです。弊社も一部関わったらしい。

Japanese Industry Meetup

弊社榊原がモデレータとなり、Japanese Industry Meetupが開催されました。主にCSS WGのメンバーと、日本語組版やデザインに関わっているもののW3C/標準化には関わっていない日本人とで話す機会を設けるものです。こちらのレポートも機会があれば後ほど掲載したいと思います。

月・火の議題ピックアップ

10/26(月)と27(火)は、CSS WGに参加しました。いくつか概要を書いてみます。

font-weight-adjust

フォントによって、同じポイント数でも見た目のサイズは微妙に異なります。

システムによってインストールされているフォントは違うため、font-sizeを厳密に指定しても、1番目のフォントがインストールされておらずフォールバックフォントが利用された場合は、見た目上のフォントサイズがずれてしまう可能性があります。

そこでこの問題を解決するために、font-size-adjustプロパティが制定されています[1]。

font-sizeでも同じ問題は発生するため、font-size-adjustのようにfont-weightプロパティでも同じ問題が発生するため、font-weight-adjustプロパティが提案されました

しかし、これは@font-faceルールでfont-weightを指定することで同じことができる(フォントプロパティ上は細いが実際は太い場合、@font-faceで太めのfont-weightを指定すれば良い)ため、却下されました。

[1]Editor’s draftが作られたきり更新される気配のないtext-size-adjustプロパティとは関係ない機能なのでご注意ください。

Overflow Alignment

以下のようなHTMLがあったとします。

<div style="display:flex; justify-content: center;">
  <p>コンテンツA</p>
  <p>コンテンツB</p>
</div>

これは改行しないflexなので、もしコンテンツが大きかったりたくさんあると、あふれます。center指定してあるので、左右に均等にoverflowすることになります。

もしこのflex container (親div)が画面の左端に配置していたらどうなるでしょうか?画面の外まではみ出してしまいます。これでは、仮にoverflow: visibleしていても見ることができません。

そこで、「center配置するが、もしはみ出るようならstart配置にする」というのを実現する機能が safeです。画面内側にはみ出れば、読めないよりはましです(スクロール等の対策も可能になります)。
trueはoverflowしても気にせず指定したcenterのままにします。

align-content: safe center;

この機能について、以下のような点が議論されました。

どこまで適用すべきか

text-alignなどにも適用することを視野に入れつつ、現在のところはまずflex/gridでということになるようです。

safe/trueどちらをデフォルトとすべきか

trueがデフォルトとなりました。指定を忘れるとはみ出る可能性があるものの、safeがデフォルトではかえってわかりづらいという意見が大半でした。

プロパティ粒度やネーミングはどうか

機能自体に関しては、十分良いだろうというところで大きな反対はありませんでした。trueがデフォルトなのだからtrueを削除してはどうかという声もありましたが、両方維持になりました。
ネーミングについては、”true”がやや微妙なので、より良い名前が募集されています。

Wide gamut

Webは長らくsRGBのみを対象にしてきましたが、広色域ディスプレイも普及し、Adobe RGBなどより広い色域への対応が本格的に検討されています。

  • タグなし画像はsRGBと仮定するのが現実的で安定するため、 color-correctionプロパティは削除されました。
  • タグ付き画像はそのタグに従えば良いですが、色番号指定も広色域に対応すべきです。background-color: rgb("adobe rgb", 0, 0, 255);のような形で指定するのが良いのか、アルファ付き4引数と混ざるためicc-colorのような専用のキーワードを追加するか、@ruleのような書式にすべきか、などが議論されましたが、その場で結論は出ず、Editorが提案を作成することになりました。

Scroll snap

Issueリストに従って1つずつ検討されました。

Scroll snapの仕様を把握していないうえに内容が多く把握しきれなかったので、詳細はIRCをご覧ください;;

MS EdgeとSafariで挙動が違う部分をどちらにすべきか、なども話し合われていたようです。

続く

CSS WGだけで2日間がっつりやっているため、まだまだ内容はあります。
後ほど続きを更新予定です。

error: reference to ‘random_access_iterator_tag’ is ambiguous

$
0
0

メモ:

GCC (not Clang) とlibc++な環境で、boost/unordered_mapを使うとこのエラーが出る。(boost 1.59)

boost/move/detail/iterator_traits.hpp:72:12:
error: reference to 'random_access_iterator_tag' is ambiguous

たとえばChromium Androidはlibc++とGCC4.9。

リポジトリではそのままのエラー修正が既にされているので、1.60を待てない場合はこのファイルだけ新しいものを持ってくれば直る。
https://github.com/boostorg/move/commit/172d49cf5464be290282e12df927571ac3a8ec66

最新ではついでにリファクタされてたので依存ファイルも忘れずに。

要するにlibc++だとstd::__1に実体を定義してusingしているということを初めて知った。

ちなみにFreeBSDだとこんな解決方法らしいけど…

Rubyのパラメータと引数の対応付けを理解する(前編)

$
0
0

こんにちは、hachi8833です。今回は英文技術ブログ記事「Ruby Methods, Procs and Blocks」を参考に、Rubyのパラメータと引数についてまとめました。

分量が元記事よりもかなり増えたので、パラメータの話を前編、引数の話を後編として分割いたします。

元記事について

原著者の許諾を得て追記・再構成のうえ公開いたします。

元記事のコメントや訂正文もチェックしました。

Rubyのパラメータと引数の対応付けを理解する(前編)

本記事では以下のトピックについて扱います。

  • 前編
    • メソッド定義のパラメータの解説と、パラメータの正しい順序
  • 後編
    • メソッドが呼び出されたときに引数がパラメータにどのように代入されるか
    • procとブロックの関連
    • lambdaとprocの違い
    • 2種類のブロック{ ... }do ... end?の違い

用語「パラメータ」「引数」について

本記事では混乱を避けるため、元記事に基いてパラメータと引数という用語を以下のように使い分けます。

パラメータ(parameter)
メソッド定義側で書くものを指す
(厳密には「メソッドパラメータ」なのでブロック内のパラメータは含めない)
引数(argument)
メソッド呼び出し側で渡すものを指す

各パラメータの種類

元記事では、Rubyのメソッド定義で使えるパラメータを以下の8つに分類しています。

種別
必須 a
オプション b = 2
配列分解 (c, *d)
splat *args
post-required f
キーワード g:, h: 7
ダブルsplat **kwargs
ブロック &blk

配列分解(array decomposition)とpost-requiredは、元記事著者独自の呼び方のようです。

上のパラメータをすべて使った例は以下のようになります。

def foo a, b = 2, *c, d, e:, f: 7, **g, &blk; end

パラメータの説明

注意: 元記事で使われているパラメータの種類は、パラメータを修飾する記号の書式だけでは決まりません。元記事におけるパラメータの種類は、「パラメータを修飾する記号」、「パラメータが置かれる位置(順序)」、「パラメータにどんな引数が渡されるか」を総合したものです。

  • たとえば「記号が何もついていないから必須パラメータ」とは限らず、パラメータの配置に応じてpost-requiredパラメータと呼んでいます。
  • 3.の配列分解はパラメータの書式ではなくパラメータの動作と理解するのがよいでしょう。

1. 必須

必須パラメータは、呼び出し側で引数を与えないとエラーになるパラメータです。つまりこの「必須」は「呼び出し側で引数を省略できない」という意味になります。もっとも普通のパラメータと言ってよいでしょう。

以下は、必須パラメータに引数を渡さなかった場合のエラー表示です。

def foo(a)        # aが必須パラメータ(普通のパラメータ)
  puts "a: #{a}"
end
foo     #=> エラー

2. オプション

パラメータにデフォルト値を与えたものはオプションのパラメータになります。オプションなので、引数を渡さない場合はデフォルト値が使われ、エラーになりません。

以下は、オプションパラメータに引数を渡さなかった場合の結果です。

def foo(a = 1)  # a = 1がオプションパラメータ(デフォルト値を持つ)
  puts "a: #{a}"
end
foo     #=> a: 1

特に、opts = {}のような形式のオプションパラメータにすると、key-valueペアをいくつでもまとめて受けることができます。key-valueペアがひとつもない場合でもエラーになりません。

def foo(a, b, opts = {})  # opt = {}でkey-valueペアを受けられる
  puts "a: #{a}"
  puts "b: #{b}"
  puts "opts: #{opts}"
end

foo("bar", "baz", { hoge: 1, huga: 2, hoga: 3 })
#=> a: bar
#=> b: baz
#=> opts: {:hoge=>1, :huga=>2, :hoga=>3}

opts = {}形式のオプションパラメータの注意点については以下もご覧ください。

3. 配列分解

配列分解は、入れ子になった配列をリテラルで(=変数に入れずに)渡された場合に配列を部分に切り分けて受け取る、特殊な動作です。

分解のパターンは、パラメータ側のsplat(後述)の配置や丸かっこ( )によって複雑に変化します。引数に[1, 2], 3を与えた場合の例を以下に示します。

(*a)          # a = [[1, 2], 3]
(a, b)        # a = [1, 2], b = 3
(a, *b)       # a = [1, 2], b = [3]
(a, b, *c)    # a = [1, 2], b = 3,   c = []
((a, b), c)   # a = 1,      b = 2,   c = 3
((a, *b), c)  # a = 1,      b = [2], c = 3

かなり複雑ですね。パズルみたいで楽しそうですが、パラメータの数が増えると、入れ子の配列リテラルの分解を正確に理解して引数で渡すのは至難の業です。引数に配列リテラルをうかつに書くと落とし穴にハマりそうです。

少なくとも、気が付かずに配列分解を引き起こすコードや、配列分解を前提としたコードを書くのは避けるのがよいでしょう。

上を実行可能なRubyコードにしてみました。なお、コード中のセミコロン;はpryのエコーバックを抑制するために追加しました。

def method1(*a)
  puts "a: #{a}"
end;
def method2(a, b)
  puts "a: #{a}"
  puts "b: #{b}"
end;
def method3(a, *b)
  puts "a: #{a}"
  puts "b: #{b}"
end;
def method4(a, b, *c)
  puts "a: #{a}"
  puts "b: #{b}"
  puts "c: #{c}"
end;
def method5((a, b), c)
  puts "a: #{a}"
  puts "b: #{b}"
  puts "c: #{c}"
end;
def method6((a, *b), c)
  puts "a: #{a}"
  puts "b: #{b}"
  puts "c: #{c}"
end;
method1 [1, 2], 3;      # a: [[1, 2], 3]
method2 [1, 2], 3;      # a: [1, 2], b: 3
method3 [1, 2], 3;      # a: [1, 2], b: [3]
method4 [1, 2], 3;      # a: [1, 2], b: 3, c: []
method5 [1, 2], 3;      # a: 1, b: 2, c: 3
method6 [1, 2], 3;      # a: 1, b: [2], c: 3

4. splat

パラメータ文字の前に*を追加すると、長さが不定の引数リスト(可変長引数リスト)を受けられるようになります。これをsplatパラメータと呼んでいます。上の配列分解の例にも、splatパラメータが含まれているものがあります。

splatパラメータは、さまざまな種類の引数をいくつ渡してもすべて受け取れます。

def foo(*a)       # *aがsplatパラメータ
  puts "a: #{a}"
end

foo :one, "two", [:three, "four"]
#=> a: [:one, "two", [:three, "four"]]

5. post-required

splatパラメータの直後に置いた必須パラメータは、post-requiredパラメータとして動作します(本記事では英ママで表記します)。
この場合ちょうどsplatパラメータのストッパーのような働きをし、splatパラメータに渡される引数リストの末尾の引数を受け取ります。

以下の例では、bがpost-requiredパラメータとして動作します。

def foo(*a, b)    # bがpost-requiredパラメータ
  puts "a: #{a}"
  puts "b: #{b}"
end

foo :one, "two", {key1: "value"}, [:three, "four"]
#=> a: [:one, "two", {:key1=>"value"}]
#=> b: [:three, "four"]

この動作からわかるように、post-requiredパラメータがあると、直前のsplatパラメータに吸い込まれる引数が1つ少なくなります

気が付かずにpost-requiredパラメータを形成したり、post-requiredパラメータを前提としたコードを書くのは、これまた避けるのがよさそうです。

6. キーワード

パラメータ名にRubyのシンボルの書式「キーワード:」を使うと、キーワード付きパラメータになります(キーワード付き引数と呼ばれることもよくあります)。

def foo(name)          # ただの必須パラメータ
  puts "name: #{name}"
end

def foo(name:)         # キーワード
  puts "name: #{name}"
end

def foo(name: "bar")   # デフォルト値付きキーワード
  puts "name: #{name}"
end

キーワード付きパラメータの最大のメリットは、メソッド呼び出し側でのキーワード付き引数の順序が不問になることです。メソッドを呼び出す側にとっては順序を気にしなくてよくなるので大変使いやすくなります。

  • キーワード付きパラメータにデフォルト値を与えないと引数が必須になります
    • 引数がない場合や、またはキーワードが示されていない場合にエラーになります
  • デフォルト値を与えれば引数がなくてもエラーになりません

特にデフォルト値付きキーワードは、メソッド呼び出し側で{key: "value"}のようなハッシュでのオプション引数渡しと同じ感覚で、しかも順序不問でkey-valueペアのオプションを渡せるので便利です。

def foo(name: "bar", addr: "tokyo", tel: "03-9999-9999")
  puts "name: #{name}"
  puts "addr: #{addr}"
  puts "tel:  #{tel}"
end

foo(tel: "03-0000-0000", addr: "shibuya", name: "baz") # 呼び出し側で順序を気にしなくてよい
#=> name: baz
#=> addr: shibuya
#=> tel:  03-0000-0000

参考

7. ダブルsplat

パラメータ名の前に**を付けるとダブルsplatパラメータになります。splatパラメータと似ていますが、可変長のキーワード引数リストを受け取る前提である点が異なります。ダブルsplatは、上述のopts = {}形式のオプションパラメータと同様の動作です。

def foo(**profile)            # ダブルsplat
  puts "profile: #{profile}"
end

foo(tel: "03-0000-0000", addr: "shibuya", name: "baz")
#=> profile: {:tel=>"03-0000-0000", :addr=>"shibuya", :name=>"baz"}

なお、可変長のキーワード引数リストをダブルでないsplatパラメータで受けると、配列の中にハッシュが保存されるので取り出しに一手間余計にかかります。

def foo(*profile)              # ただのsplat
  puts "profile: #{profile}"
end

foo(tel: "03-0000-0000", addr: "shibuya", name: "baz")
#=> profile: [{:tel=>"03-0000-0000", :addr=>"shibuya", :name=>"baz"}]   <=配列の中にハッシュがある

8. ブロック

最後のブロックは、他のパラメータと異なる点があります。

  • メソッド定義でyieldでブロックを実行する場合は、パラメータに何も書かなくても、呼び出し側で引数リストの最後にブロックを置くことで渡せます(以下: 暗黙のブロックパラメータ)。
  • 逆に、パラメータの末尾に&付きのブロックパラメータを明示的に置いてブロックを受けることもできます(以下: 明示的なブロックパラメータ)。
# 暗黙
def foo              # 普通のパラメータも、ブロックを受けるパラメータもない
  yield
end

foo { puts "hello" } #=> hello

# 明示
def foo &blk         # &blkはブロック専用のパラメータ
  blk.call
end

foo { puts "hello" } #=> hello

暗黙のブロックパラメータで受けたブロックには名前がありません。

明示的なブロックパラメータで受けたブロックには名前があるので、他のメソッドにブロックを渡したりできるようになります。後編の「ブロックとproc」を参照してください。

参考: #parametersメソッド

Method#parametersメソッドを使うと、メソッドのパラメータリストを取得できます。取得したリストには、上で説明したパラメータの種別も含まれます。先ほどのfooメソッドに対してこのメソッドを実行すると以下の結果が得られます。

method(:foo).parameters
# [[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :g], [:block, :blk]]

配列分解パラメータだけは単に[:req]と表示されます。配列分解は特殊な動作なので、一意の表現形式がないのだと思います。

パラメータの順序

以上を踏まえて、いよいよメソッド定義でのパラメータの順序について述べます。

上述のパラメータをメソッド定義で好きな順序で使うことはできません。以下の順序を守って記述する必要があります(冒頭の表とパラメータの説明もこの順序に沿っています)。

1:必須パラメータ
a
2:デフォルト値付きオプションパラメータ
b = 1
3:splatパラメータ(1つまで)
*c
4:post-requiredパラメータ
*c, dの「d」の方
5:キーワードパラメータ
e:e: 1
6:ダブルsplatパラメータか
オプションハッシュパラメータ
1つまで)
**ff = {}
7:ブロックパラメータ(1つまで)
{ }do ... endなど

避けたいパラメータ

以下はなるべく避けることをおすすめします。

  • 必須パラメータとオプションパラメータの順序を混在させる
  • post-requiredパラメータの利用
  • オプションパラメータとsplatパラメータの同時利用
  • 配列分解を前提とした引数渡し

パラメータにデフォルト値を与えたいのであれば、オプションパラメータではなくキーワードパラメータを使いましょう。

補足: Rubyのキーワードパラメータ

上述のとおり、Rubyのキーワードパラメータを使うと、呼び出し側でキーワード引数の順序が不問になります。このおかげで、デフォルト値付きのオプションパラメータを、ハッシュでのオプション渡しと同じように(かつもう少し行儀よく)扱うことができ、Rubyの使い勝手のよさにつながっています。

# キーワード引数方式
def foo(name: "bar", addr: "tokyo", tel: "03-9999-9999")
  puts "name: #{name}"
  puts "addr: #{addr}"
  puts "tel:  #{tel}"
end;

# オプションハッシュ方式
def bar(opts = {})
  puts "name: #{opts[:name]}"
  puts "addr: #{opts[:addr]}"
  puts "tel:  #{opts[:tel]}"
end;

# どちらも呼び出し方は同じ、かつ順序不問
foo(tel: "03-0000-0000", addr: "shibuya", name: "baz");
bar(tel: "03-0000-0000", addr: "shibuya", name: "baz");
#=> name: baz
#=> addr: shibuya
#=> tel:  03-0000-0000

メソッド定義にないkey-valueペアを引数として与えると、キーワードパラメータの場合はエラーになります。こういうところが行儀よいですね。
オプションパラメータの場合はすべてパラメータに渡されるのでエラーにはなりません。

# キーワードパラメータにないキーワード引数を与えるとエラー
foo(tel: "03-0000-0000", addr: "shibuya", name: "baz", email: "ex@ex.com");
#=> ArgumentError: unknown keyword: email

# オプションパラメータは何でも飲み込む
bar(tel: "03-0000-0000", addr: "shibuya", name: "baz", email: "ex@ex.com");
#=> name: baz
#=> addr: shibuya
#=> tel:  03-0000-0000

なお、Objective-Cのキーワード引数は定義と同じ順序で渡さないといけないのだそうです。

また、Javaでは「メソッド名」「引数の数」「引数の型」をシグネチャと呼び、(引数の順序も含めて)シグネチャが1つでも異なっていれば同じクラス内でも異なるメソッドとみなされるのだそうです。同じ名前で複数のメソッドを定義できるようにするためのメカニズムであるようです。

同じクラスで同名メソッドを複数定義できないRubyとはかなり考え方が違いますね。

今回はここまでとします。後編にご期待ください。

関連記事

Rubyスタイルガイドを読む: 正規表現、%リテラル、メタプログラミング(最終回)

$
0
0

こんにちは、hachi8833です。Rubyスタイルガイドを読むシリーズは今回で最終回となります。

今後総集編として修正・アンカー追加などを行って1本の記事にまとめますので、今後はそちらをご覧ください。原文末尾のツールなどの記述は総集編に含めます。

Rubyスタイルガイド: 正規表現、%リテラル、メタプログラミングなど

正規表現

ひとつの問題に突き当たると「おっしゃ、正規表現の出番だ」と考えるタイプの人は、問題を2つに増やす。
— Jamie Zawinski

9-01【統一】「文字列変数['テキスト']」で単純な文字列を検索するのであれば正規表現にしないこと

Don’t use regular expressions if you just need plain text search in string: string['text']

正規表現厨の私も少々反省をうながされてしまいました。コードのシンプルさにおいてもパフォーマンスにおいても正規表現による検索は単なる文字列の場合と比べて見劣りします。上の指示は、「正規表現は本当に必要なときにだけ使うこと」を含意していると思います。

原文にないコード例を以下に追加してみました。

str = "Hello, Ruby!"
# 不可
if str[/Hello/].nil?
  (省略)
end

# 可
if str['Hello'].nil?
  (省略)
end

#良好(よりRubyらしい)
if str.includes? 'Hello'
  (省略)
end

単純な文字列の存在チェックであれば#match?で文字列を指定するほうがシンプルな気もします。

補足: 文字列変数に配列っぽくアクセスする

Rubyでは、文字列変数に[ ]を追加することで配列「風」にアクセスできます。インデックスにあたる部分には単なる数値以外に範囲も指定できます。

str = 'Hello, Ruby!'
puts str[0]         #=> H
puts str[0, 4]      #=> Hell
puts str[0..4]      #=> Hell

私も今回初めて知りましたが、この配列風文字列のインデックスには数値以外に文字列や正規表現を直接書くこともできます。文字列を直接書けるのであれば、必要もないのにわざわざ正規表現で書くこともありませんね。

str = 'Hello, Ruby!'
puts str['Hell']    #=> Hell
puts str['Hi']      #=> nil

puts str[/[Hh]ell/] #=> Hell
puts str[/^Hi$/]    #=> nil

補足: 文字列置換の場合

文字列置換によく使われる#gsub#subでは、正規表現でない文字列でも使えます。
また、特定の目的については便利なメソッドが既にあります。

以下も原文にないコード例です。

# 不可
'test.rb'.gsub(/\.rb/, '')
'   Hello, Ruby!   '.gsub(/^[ ]+|[ ]+$/, '')

# 良好
'test.rb'.tr('.rb', '')     # 文字列を取り除くならtrなどがよい
'test.rb'.gsub('.rb', '')   # 正規表現を使わないgsubでもよい

'   Hello, Ruby!   '.strip  # 先頭・末尾の空白を取り除くならstripがよい

9-02【選択】コードをシンプルにするために、文字列のインデックスに正規表現を直接書いてもよい

For simple constructions you can use regexp directly through string index.

上述の項で補足した「文字列のインデックスに正規表現を直接書く」方法も選択肢に含めてよいということですね。
コード例は原文のままだと少々わかりにくいので変更しました。

str = 'Hello, Ruby'

puts match = str[/[Hh]ello/]                # 正規表現にマッチする部分文字列をstrから取り出す
#=> "Hello"

puts first_group = str[/^Hello, (.+?)$/, 1] # キャプチャグループ1を取り出す
#=> "Ruby"

str[/^Hello, (.+?)$/, 1] = 'MRI'            # strのキャプチャグループ1を置き換える
puts str
#=> "Hello, MRI"

9-03【統一】【ヒント】正規表現でキャプチャの結果が不要な場合はキャプチャなしグループ(?:)を使うこと

Use non-capturing groups when you don’t use the captured result.

正規表現で丸かっこ()を使った部分はキャプチャされ、番号を指定して後で取り出すことができます。
番号の代わりに(?'名前'パターン)などのようにグループに名前を付けることもできます。

キャプチャが発生するとその分パフォーマンスが下がることが考えられるため、それを避けるためにキャプチャなしグループ(?:)を使うのだと思います。

# 不可
/(first|second)/

# 良好
/(?:first|second)/

(?:pat)という記法を使うとキャプチャせずにグループ化することができます。 性能が多少改善する場合がありますが、多少見にくくなります。
Rubyリファレンスマニュアル: 正規表現より

ループ内などで正規表現を頻繁に使う場合は考慮しておくとよいと思います。

9-04【統一】最後にマッチしたグループの取り出しにPerl由来の$記法($1$2など)を使わないこと

Don’t use the cryptic Perl-legacy variables denoting last regexp group
matches ($1, $2, etc). Use Regexp.last_match(n) instead.

代わりにRegexp.last_match(n)を使うよう指示しています。
$%$^$#といったいかにも間に合わせでこしらえたような$記法は、どうしてもクイズになってしまい可読性が落ちますね。

Perlとは無関係に、$が多い記法は個人的にも目にイガイガを感じてしまいます。LaTeXとか。

/(regexp)/ =~ string
...

# 不可
process $1

# 良好
process Regexp.last_match(1)

$を使わない記法については以下の記事をご覧ください。

9-05【統一】キャプチャグループは番号での指定ではなく名前付きグループでの指定が望ましい

Avoid using numbered groups as it can be hard to track what they contain. Named groups can be used instead.

番号でのキャプチャグループは、キャプチャ数が増えたりネストしたりしたときに追うのが本当につらいので、自分のためにもなるべく名前付きキャプチャを使いましょう。

# 不可
/(regexp)/ =~ string
# (何かする)
process Regexp.last_match(1)

# 良好
/(?<meaningful_var>regexp)/ =~ string
# (何かする)
process meaningful_var

9-06【統一】文字クラス[ ]の中ではドット.やかっこの類をエスケープしないこと

Character classes have only a few special characters you should care about:
^, -, \, ], so don’t escape . or brackets in [].

文字クラスを表す[ ]の中でエスケープが必要なのは、実は以下の文字だけです。これら以外の文字・記号は文字クラス内ではただのリテラルとして扱われます。

^
文字クラスの否定を表すのに使う
[^0-9]: (0から9以外のすべての文字)
-
文字の範囲を表すのに使う
[0-9a-zA-Z0-9a-zA-Z]: (全角半角英数字)
\
エスケープ文字
[@#$%\^&*\\]: (^\\でエスケープしている)
]
文字クラスの終了を表す
[[\]]: (実は]だけエスケープすればよい)

上はもちろん、文字クラス[ ]の中だけの話です。

9-07【必須】パターンの冒頭と末尾は^$ではなく、\A\zで表すこと

Be careful with ^ and $ as they match start/end of line, not string endings. If you want to match the whole string use: \A and \z (not to be confused with \Z which is the equivalent of /\n?\z/).

有名な話ですが、Ruby、PHP、Perlなどの言語で使われている正規表現ライブラリで文の冒頭と末尾を^$で表すと、改行文字\nを含むパターンを食べさせるインジェクションに対して脆弱になることがあります。

これを避けるために、パターンで文頭と文末を表すときは必ず\A\zを使いましょう

string = "some injection\nusername"
string[/^username$/]   # マッチしてしまう
string[/\Ausername\z/] # マッチしない

少々紛らわしいのですが、後者は大文字ではなく小文字の\zにしなければなりません。大文字の\Zにしてしまうと、末尾に改行がある場合にマッチしてしまうので対策として不完全です。

念のため、Rubyリファレンス・マニュアルから引用します。

アンカーは幅0の文字列にマッチするメタ文字列です。 幅0とは文字と文字の間、文字列の先頭、文字列の末尾、などを意味します。ある特定の条件を満たす「位置」にマッチします。
^ 行頭にマッチします。行頭とは、文字列の先頭もしくは改行の次を意味します。
$ 行末にマッチします。 行末とは文字列の末尾もしくは改行の手前を意味します。
\A 文字列の先頭にマッチします。
\Z 文字列の末尾にマッチします。 ただし文字列の最後の文字が改行ならばそれの手前にマッチします。
\z 文字列の末尾にマッチします。
\b 単語境界にマッチします。 単語を成す文字と単語を成さない文字の間にマッチします。 文字列の先頭の文字が単語成す文字であれば、文字列の先頭 の位置にマッチします。
\B 非単語境界にマッチします。 \bでマッチしない位置にマッチします。
Rubyリファレンスマニュアル: 正規表現より

9-08【統一】複雑な正規表現はx修飾子で読みやすくすること

Use x modifier for complex regexps. This makes them more readable and you can add some useful comments. Just be careful as spaces are ignored.

x修飾子を指定するとスペースが無視されるので、以下のようにパターンを分解して行別に記述し、読みやすくできます。さらに#でコメントを書くこともできますので、使わない手はありませんね。私も今回初めて知りました。

その代わりスペースを直接書けなくなるので、\sなどで表す必要があります。なお私は個人的に[\p{Zs}]を使っています。

regexp = /
  start         # テキスト
  \s            # スペース文字
  (group)       # 最初のキャプチャグループ
  (?:alt1|alt2) # 文字列のいずれかに一致
  end
/x

なお、/xオプションを指定しない場合のコメントは(?# )で書けます。

regexp = /(?#ここにコメントを書く)Hello/

9-09【ヒント】複雑な置換では、#sub#gsubにブロックやハッシュを与えてもよい

For complex replacements sub/gsub can be used with a block or a hash.

うまく使うと可読性が上がりそうですね。いいことを知りました。

words = 'foo bar'
words.sub(/f/, 'f' => 'F')                   # => 'Foo bar' (ハッシュを与える場合)
words.gsub(/\w+/) { |word| word.capitalize } # => 'Foo Bar' (ブロックを与える場合)

%リテラル

9-10【統一】二重引用符を含む1行の文字列を式展開する場合は%()%Qの省略形)を使う: 複数行ならヒアドキュメントを使う

Use %()(it’s a shorthand for %Q) for single-line strings which require both interpolation and embedded double-quotes. For multi-line strings, prefer heredocs.

以下の3つの条件を満たす場合は%( )を使います。

  • 式展開(#{ })を含む
  • 二重引用符(")を含む
  • 1行に収まる
# 不可(式展開がないなら'<div class="text">Some text</div>'とすべき)
%(<div class="text">Some text</div>)

# 不可(二重引用符がないなら"This is #{quality} style"とすべき)
%(This is #{quality} style)

# 不可(複数行ならヒアドキュメントにすべき)
%(<div>\n<span class="big">#{exclamation}</span>\n</div>)

# 良好(式展開が必要かつ二重引用符を含む1行の文字列は条件を満たす)
%(<tr><td class="name">#{name}</td>)

9-11【統一】%()(または同等の%q())は、一重引用符と二重引用符を両方含む文字列以外では使わないこと

Avoid %() or the equivalent %q() unless you have a string with both ' and " in it. Regular string literals are more readable and should be preferred unless a lot of characters would have to be escaped in them.

エスケープしなければならない文字がたくさんあるのでなければ、読みやすい普通の文字列リテラルを使います。

# 不可
name = %q(Bruce Wayne)
time = %q(8 o'clock)
question = %q("What did you say?")

# 良好
name = 'Bruce Wayne'
time = "8 o'clock"
question = '"What did you say?"'
quote = %q(<p class='quote'>"What did you say?"</p>)

9-12【統一】正規表現リテラル%rは、正規表現に/文字が含まれていなければ使わないこと

Use %r only for regular expressions matching at least one ‘/’ character.

確かに、必要もないのにわざわざ%rで可読性を下げることはありませんね。

# 不可
%r{\s+}

# 良好
%r{^/(.*)$}
%r{^/blog/2011/(.*)$}

9-13【統一】コマンドリテラル%xは避ける(コマンド自体にバッククォートがある場合を除く)

Avoid the use of %x unless you’re going to invoke a command with backquotes in it(which is rather unlikely).

コマンドリテラルにさらにバッククォートを含めないといけないようなシチュエーションは、そうなさそうです。

# 不可
date = %x(date)

# 良好
date = `date`
echo = %x(echo `date`)

9-14【統一】シンボルリテラル%sの利用は避けること

Avoid the use of %s. It seems that the community has decided :"some string" is the preferred way to create a symbol with spaces in it.

Rubyコミュニティでは、スペース含みのシンボル作成は原則として:"some string"のような方法で行うこと、と決定したそうです。

9-15【統一】%リテラルで使うかっこは、リテラルの種類に応じて使い分けること

Use the braces that are the most appropriate for the various kinds of percentliterals.

Rubyでは%リテラルで使う囲み記号としてさまざまなものを使えますが、ともするとスタイルが不揃いになりがちなので、これで統一するということですね。

() 丸かっこ
文字列リテラル: %q%Q
その他のリテラル: %s%x
[] 角かっこ
配列リテラル: %w%i%W%I
理由:通常の配列記法と整合するため
{} 波かっこ
正規表現リテラル: %r
理由:正規表現内では丸かっこ( )が多用されるため、正規表現での出現率が低い{ }が適切と判断
# 不可
%q{"Test's king!", John said.}

# 良好(文字列リテラルは丸かっこで)
%q("Test's king!", John said.)

# 不可
%w(one two three)
%i(one two three)

# 良好(配列リテラルは角かっこで)
%w[one two three]
%i[one two three]

# 不可
%r((\w+)-(\d+))
%r{\w{1,2}\d{2,5}}

# 良好(正規表現リテラルは波かっこで)
%r{(\w+)-(\d+)}
%r|\w{1,2}\d{2,5}|

メタプログラミング

本スタイルガイドでは、メタプログラミングについて全般に慎重な立場を取っています。

9-16【統一】必要のないメタプログラミングは避けること

Avoid needless metaprogramming.

メタプログラミングは「やってみたかったから」という理由で導入されることがかなり多いように思えます。どんな場合に必要になるかですが、メタプログラミングでないとコードが書けないという事態は考えにくいので、ActiveRecordなど既存のメタプログラミングを拡張、改修する場合や、メタプログラミングの方がコードが簡潔かつ拡張しやすくなることが明らかな場合が主になるのかもしれません。

もちろん自分しか使わないことが確実なコードであれば存分に使ってもよいと思います。

9-17【統一】コアクラスにモンキーパッチを当てないこと

Do not mess around in core classes when writing libraries. (Do not monkey-patch them.)

ライブラリを書くときにコアクラスを汚してはならない、だそうです。もっともです。

9-18【統一】class_evalは文字列の式展開形式ではなくブロック形式が望ましい

The block form of class_eval is preferable to the string-interpolated form.

  • 文字列の式展開形式にする場合は、常に__FILE____LINE__を追加してバックトレースを読みやすくすること
  • when you use the string-interpolated form, always supply __FILE__ and __LINE__, so that your backtraces make sense:
class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__
  • define_methodの方がclass_eval{ def ... }よりも望ましい

define_method is preferable to class_eval{ def ... }

9-19【統一】文字列の式展開でclass_evalなどのevalメソッドを使う場合は、式展開を具体的に示すコメントを追加すること

  • When using class_eval (or other eval) with string interpolation, add a comment block showing its appearance if interpolated (a practice used in Rails code):

以下はRailsのActiveSupportのコードです。右側のコメントで示されているのが#capitalizeメソッドを式展開した例です。

# activesupport/lib/active_support/core_ext/string/output_safety.rb より
UNSAFE_STRING_METHODS.each do |unsafe_method|
  if 'String'.respond_to?(unsafe_method)
    class_eval <<-EOT, __FILE__, __LINE__ + 1
      def #{unsafe_method}(*params, &block)       # def capitalize(*params, &block)
        to_str.#{unsafe_method}(*params, &block)  #   to_str.capitalize(*params, &block)
      end                                         # end

      def #{unsafe_method}!(*params)              # def capitalize!(*params)
        @dirty = true                             #   @dirty = true
        super                                     #   super
      end                                         # end
    EOT
  end
end

メタプログラミングは抽象度が高いので、こういう具体的な展開をコメントで添えておくことでずっと読みやすくなります。やっておかないと後できっと自分もつらくなります。

9-20【統一】method_missingは避け、代わりに委譲、プロキシ、define_methodの利用を検討すること

Avoid using method_missing for metaprogramming because backtraces become messy, the behavior is not listed in #methods, and misspelled method calls might silently work, e.g. nukes.launch_state = false.
Consider using delegation, proxy, or define_method instead.
If you must use method_missing:
– Be sure to also define respond_to_missing?
– Only catch methods with a well-defined prefix, such as find_by_* — make your code as assertive as possible.
– Call super at the end of your statement
– Delegate to assertive, non-magical methods:

method_missingによるメタプログラミングはバックトレースが非常に追いづらく#methodsでメソッドリストを取ることもできません。nukes.launch_state = falseのようなスペルの間違ったメソッド名が正規表現をすり抜けて思わぬ動作を引き起こす可能性すらあります。

IDEのコード定義ジャンプも効かなくなりますね。

method_missingの利用がどうしても避けられない場合は、以下を必ず守らなければなりません。

  • respond_to_missing?も定義すること
  • find_by_*など、確実に定義されたプレフィックスだけをキャッチすること
  • 文の最後で#superを呼ぶこと
  • 明示的に定義された(=黒魔術系でない)メソッドに委譲すること
# 不可
def method_missing?(meth, *params, &block)
  if /^find_by_(?<prop>.*)/ =~ meth
    # ここでがんばってfind_byを長々と実装する
  else
    super
  end
end

# 良好
def method_missing?(meth, *params, &block)
  if /^find_by_(?<prop>.*)/ =~ meth
    find_by(prop, *params, &block)    # 黒魔術でないメソッドを呼んでいる
  else
    super
  end
end
# ただし検索可能な属性を宣言できるdefine_methodがやはりベスト

9-21【統一】sendは避け、public_sendを使うこと

Prefer public_send over send so as not to circumvent private/protected visibility.

public_sendはprivateやprotectのスコープをすり抜けないことがその理由です。

# OrganizationというActiveModelがあり、Activatableというconcernがあるとする
module Activatable
  extend ActiveSupport::Concern

  included do
    before_create :create_token
  end

  private

  def reset_token
    # コード
  end

  def create_token
    # コード
  end

  def activate!
    # コード
  end
end

class Organization < ActiveRecord::Base
  include Activatable
end

linux_organization = Organization.find(...)

# 不可(privateを呼べてしまう)
linux_organization.send(:reset_token)

# 良好(期待どおり例外が発生する)
linux_organization.public_send(:reset_token)

9-22【統一】sendよりもアンダースコア付きの__send__が望ましい

Prefer __send__ over send, as send may overlap with existing methods.

sendは既存のメソッドをオーバーライドする可能性があるためです。

require 'socket'

u1 = UDPSocket.new
u1.bind('127.0.0.1', 4913)
u2 = UDPSocket.new
u2.connect('127.0.0.1', 4913)

# レシーバーオブジェクトにメッセージを送信しない
# 代わりにUDPソケット経由でメッセージを送信する
u2.send :sleep, 0

# レシーバーオブジェクトに実際にメッセージを送信する
u2.__send__ ...

その他

9-23【統一】ruby -wオプションを使って安全なコードを書くこと

Write ruby -w safe code.

警告を無視してはならないということですね。忙しいときでも一度はやっておきましょう。

9-24【統一】オプションパラメータにハッシュを使うことは避ける

Avoid hashes as optional parameters. Does the method do too much? (Object initializers are exceptions for this rule).

大量のハッシュオプションが必要になるほどメソッドの機能が多いのは、設計に問題があると考える方がよさそうです。オブジェクトの初期化メソッドでのオプションハッシュは認められます。

これはまさに「Railsフレームワークで多用される「options = {} 」引数は軽々しく真似しない方がいいという話」で書いた話ですね。

9-25【統一】1つのメソッドのコードは10行以内に収めること

Avoid methods longer than 10 LOC (lines of code). Ideally, most methods will be shorter than 5 LOC. Empty lines do not contribute to the relevant LOC.

理想は5行以内だそうです。

空行、メソッド定義とそれに対応するendはカウントしません。コメント行もカウントしなくてよいと思います。

9-26【統一】パラメータリストは最大5個までにする

Avoid parameter lists longer than three or four parameters.

パラメータ数は現実には多くなってしまいがちなので、「避ける」どまりになっています。パラメータが多いということはメソッドの機能が過剰になっている可能性があるので、メソッドの分割も必要かもしれません。

以下は原文にないサンプルです。

def deliver_mail(
  to: 'ex@example.com',
  from: 'exa@example.com',
  subject: 'My message',
  header:,
  body: )
  ...
end

9-27【統一】「グローバルな」メソッドがどうしても必要な場合は、Kernelクラスのprivateメソッドにすること

If you really need “global” methods, add them to Kernel and make them private.

具体的な書き方については以下をご覧ください。

参考

9-28【統一】グローバル変数は使わないこと: 必要な場合はモジュールのインスタンス変数を使う

Use module instance variables instead of global variables.

ただ「グローバル変数を使うな」としか書いてないと回避方法がばらついてしまうので、回避方法をスタイルガイドで統一している点が重要ですね。

# 不可
$foo_bar = 1

# 良好
module Foo
  class << self
    attr_accessor :bar
  end
end

Foo.bar = 1

9-29【統一】コマンドラインオプションが複雑になったらOptionParserを使い、細かなオプションではruby -sを使う

Use OptionParser for parsing complex command line options and ruby -s for trivial command line options.

OptionParserはRuby標準のコマンドライン解析ライブラリです。ruby -sを使うと、スクリプト名の後にもオプションを書けるようになります。

この種の解析ライブラリはたくさんあるので、もっとよいものがあればそちらを使い、開発者の間でばらつくようなら標準のものを使うということでよいと思います。

9-30【統一】理由がない限り、状態の保持を避けて関数的に書くこと

Code in a functional way, avoiding mutation when that makes sense.

原文がえらく走り書きなのですが、次の文ともからめておおよそ上のような意図だと思います。

9-31【統一】受け取ったパラメータを変更しないこと(破壊的なメソッドを除く)

Do not mutate parameters unless that is the purpose of the method.

ついでに、破壊的なメソッド名の末尾には!を付けるようにしましょう。

9-32【統一】ブロックのネストは3階層までにする

Avoid more than three levels of block nesting.

ブロックのネストが増え過ぎたら、ブロックをメソッドとして切り出すなどで回避します。

9-33【統一】コーディングスタイルを一貫させること

Be consistent. In an ideal world, be consistent with these guidelines.

言うまでもないことですね。
過去のコードをそのままに途中からスタイルを変えるのはよくありません。

9-34【統一】常識を働かせること

Use common sense.

たった一言ですが、スタイルガイドに盲目的に従うものではないと警告していると私は考えます。

本スタイルガイドには3つの側面があります。

  • A. 必須・禁止事項を示す(やるべきこと、やってはならないこと)
  • B. スタイルを示す(書き方がばらつかないために、ある程度合理的な理由に基づいた指針)
  • C. コーディング上の便利なヒント

Aはもちろん守るべきですが、Bはデフォルトのスタイルとして使うほかに、書き方に迷った場合や開発者同士で意見が割れた場合の指針にも使えます。

Bは案件によって細かな点が違うのが普通なので、スタイルを守ろうとするあまり無理な書き方になっては元も子もありません。

Cはそれらとは別に有益な技術情報として利用できます。

A、B、Cがスタイルガイドに分類なしで盛り込まれていてやや雑然としているので、元のドキュメントでもそうした情報が項目ごとに示されるとよいですね。きれいに分類できるとも限らないので難しいとは思いますが。

ご愛読いただきありがとうございました。

関連記事



EPUBビューア「超縦書」Windows版 使い方・TIPS

$
0
0

EPUBビューア「超縦書」について

EPUBビューア『超縦書』は、電子書籍フォーマット「EPUB」(3.0.1)に対応した、EPUB表示・閲覧のためのビューアアプリケーションです。以下のページにてWindows版を無料でご利用いただけます。

「超縦書」Windows版 無料公開ページ

使い方・TIPS

本ページでは、見つけづらい機能や使い方のTIPSなどをご紹介します。

ファイルの開き方

以下の方法でEPUBファイルを開くことができます。

  • EPUBファイルをダブルクリック(インストーラでインストールした場合、自動で関連付けされます)
  • 左上の ボタンからファイルを指定
  • ウィンドウにドラッグ&ドロップ
  • コマンドライン引数にファイルを指定

超画像エンジンのご利用方法

本ビューアには、「超縦書」「超画像」の2つのエンジンが同梱されており、標準の状態では「超縦書」が使用されます。

閲覧するコンテンツが画像ベースFixed Layout EPUB (電書協ガイドラインに従ったもの) であることがわかっている場合は、 超画像エンジンを使うことでより高速に閲覧することができます。
超画像エンジンを使うには、EPUBファイルの拡張子を .comic に変更して開きます。現行バージョンでは、関連付けは行われませんので、ドラッグ&ドロップや「開く」メニューからオープンしてご利用ください。

連番画像ZIPコンテンツのご利用方法

JPEGファイルをZIPでまとめたコンテンツを開くことができます。この際は超画像エンジンが利用されます。

※本機能は試験的な機能と位置づけられており、ページめくり方向や順序などの詳細な設定機能は無効化されています。 またファイル関連付けは行われませんので、ドラッグ&ドロップや「開く」メニューからオープンしてご利用ください。

アップデート通知の取得について

『超縦書』では、起動時にソフトウェアのアップデートがないかを確認する機能があります。この確認は、BPSが用意したアップデート情報配信サーバにHTTPで通信することで行われます。利用するプロキシサーバの設定を設定することで、直接HTTP通信を行えないネットワーク環境でもアップデートのお知らせを受け取ることができます。上部ツールバー、右側領域にある歯車アイコン押下で詳細設定ダイアログが開きます。そこで、詳細設定ボタンを押下するとプロキシサーバーの設定項目がありますので、お使いの環境に合わせて設定してください。

EPUBビューア「超縦書」Windows版 よくある質問

$
0
0

EPUBビューア「超縦書」について

EPUBビューア『超縦書』は、電子書籍フォーマット「EPUB」(3.0.1)に対応した、EPUB表示・閲覧のためのビューアアプリケーションです。以下のページにてWindows版を無料でご利用いただけます。

「超縦書」Windows版 無料公開ページ

よくある質問

本ページでは、お客様からいただくご質問への回答や、トラブルシューティングを記載します。

このビューアは完全にEPUB作成業務向けなのですか? EPUBファイルを普通に見たいだけなのですが

そんなことありません! 普通にビューアとしてもお使いいただけます。 個人での利用お申し込みの場合は、利用申し込みフォームの「法人名」欄に 個人 と御記載の上お申し込みください。

いろいろ難しいこと書いてありますが、使ってみていい感じなのでそのままサービス導入したいのですが

ありがとうございます。サービスへの導入については個別にご契約いただいた上、 DRM(EPUBコンテンツ保護)の組み込みなどの初期導入対応が必要となりますので、 フォームよりお問い合わせください。

エラー(003003)が発生する

EPUBファイルが仕様通りに制作されていない可能性が考えられます。 超縦書は他ビューアに比べて仕様に対して厳格なため、他ビューアで閲覧できるEPUBファイルでも本エラーが発生することがございます。

003003は、「EPUB Navigation Document (目次)が仕様に沿っていない」際に発生します。epubcheckでエラーが発生しないかご確認ください。

※現行バージョンのepubcheckで検出できないことがある既知の問題として、以下の点が見つかっています。
・href属性の指定されていない a 要素が存在する。

ウィンドウが真っ黒になり、何も表示されない

Hyper-V上のVMなどで、ディスプレイが16bit Colorなどに設定されていると、本現象が発生します。 本アプリケーションの動作には32bit Colorへの対応が必要ですので、グラフィックスドライバや設定をご確認ください

Mac版はないの?

超縦書はWindows/Mac/Android/iOSに対応しておりますが、現在はWindows版のみ無料公開しております。 他プラットフォームの無料版に関しましては、反響が大きいようでしたら検討させていただきます。

反映されないフォントがある

カスタムフォント選択プルダウンに表示されるものの、反映されないフォントがまれに存在します。具体的にはulCodePageRangeの0ビット目が立っていないフォントで、例えば「HG行書体」「HG教科書体」などが該当します。

本件は現バージョンでの制約事項となります。恐れ入りますが、別のフォントをお試しください。

余白設定が反映されない

EPUBコンテンツのCSSで@pageルールによるmargin指定がされている場合、そちらを優先する仕様となっています。

CMakeをより便利にするライブラリ “CMakeSupports”がオープンソースになりました

$
0
0

超縦書でも使っているライブラリCMakeSupportsが、オープンソースライセンスで公開されました。
https://github.com/flokart-world/cmake-supports

これは何

クロスプラットフォームなビルド補助ツールとして、CMakeは大変優秀(※1)なソフトウェアです。

CMakeSupportsは、CMake単体で手が届かなかった部分を補完するためのユーティリティ群です。超縦書の開発メンバーのskhrshin氏によって開発されており、超縦書(40以上の内部モジュールで構成)のビルドで全面的に利用されています。

現状、社内利用していたものを公開したプロトタイプ段階ですので、ドキュメントは皆無で汎用性が足りない部分もありますが、CMakeの書き心地に不満があった方は是非ともお試しください。

※1: 少なくとも、hostとtargetのオプションパラメータをごちゃ混ぜにしているGYPとはレベルが違うのだよと言いたい。GYPは作者にも見捨てられましたが。

できることの例

出力するターゲット(Executable, Libraryなど)ごとにブロック形式で書ける

サンプルコードより。

cmake_minimum_required (VERSION 3.0.0)
find_package (CMakeSupports REQUIRED 0.0.6)
project (hellowww)

CMS_BEGIN(Executable HelloWWW)
  CMS_IMPORT_PACKAGE(Boost REQUIRED)
  CMS_IMPORT_PACKAGE(OpenSSL PREFIX OPENSSL REQUIRED)

  # They don't define cached variables.
  find_package (libpion REQUIRED)
  CMS_INCLUDE_DIRECTORIES(${LIBPION_INCLUDE_DIRS})
  CMS_LINK_LIBRARIES(${LIBPION_LIBRARIES})

  CMS_ADD_SOURCE_FILES(server.cpp
                       server.hpp
                       main.cpp
                       wwwmain.cpp
                       wwwmain.hpp)

  if (MSVC)
    CMS_ADD_DEFINITIONS(_CRT_SECURE_NO_WARNINGS)
    CMS_DISABLE_MSVC_WARNINGS(4251)
  endif ()
CMS_END()

CMS_BEGINからCMS_ENDのブロックを定義することができます。

これにより、add_executableなどのコマンドで毎回ターゲット名をコピペする必要がなくなり、ターゲット名を変数化するために衝突などを考慮して規約を決める、といった手間からも解放されます。

また、全体設定とターゲットごとの設定が読みやすくなり、1ファイルで複数の小さなExecutableを書くのもやりやすくなります。さらに、Moduleを定義するだけでexportまでできるので、CMakeLists.txtをより宣言的にシンプルに保つことができます。

PackageConfig対応を強化

標準のCMakeでは、FindXXXのモジュールはCMAKE_PREFIX_PATHなどの環境変数を元に探索するものが多いです。

しかし1台のマシンでHost/Target両方向けにビルドしたり、特定プロジェクトでのみ別バージョンのライブラリを使ったりするためにも、CMAKE_PREFIX_PATHに大量のパスを追加していくのはできれば避けたいところです。

CMakeSupportsではいくつかのライブラリ(libjpegやlibxml2等)についてPackageConfigを使って探索するモジュールを同梱しています。これにより、PKG_CONFIG_PATHによって複数のライブラリセットを簡単に切り替えられるだけでなく、依存ライブラリ(たとえばlibxml2では.pcファイルにLibsセクションでiconvへの依存が書いてあるなど)への対応もしやすくなります。

ドキュメント

まだ用意されていません。

サンプルminizipなどは公開されているので、それを参考にソースを読んでください、というステータスです。

気になる方がいたら、GitHubのissuesに登録していただければ作者から返信がつくと思います。

関連記事

EPUB制作: 目次Navigation Documentのよくある間違い

$
0
0

EPUB Navigation Documentは、EPUB内に含まれる目次に相当するXHTMLファイルです。

今回は、社内テストやお客様からの問い合わせで発見したコンテンツ制作上の間違いなどをご紹介します。

EPUB Navigation Documentの書き方

なお、ここでは特に断りのない限りEPUB 3.1のEPUB Navigation Documentを参照しますが、3.0や3.0.1でも基本的には同じです。

基本的な構造

必須となっており、多くのビューアでも主に使われるtocタイプを例に挙げます。あくまで概要ですので、詳しくはNavigation Documentの仕様をご確認ください。


  • nav[type="toc"]がある
    • navは0個または1個のheading content(h1-h6)を含む(目次全体のタイトルを書ける)
    • navは必ず1個のolを含む
    • olは1個以上のliを含む(項目が1個もない目次は許可されない)
      • liaまたはspanをどちらか必ず1個含む
        • aはContent Documentへのリンク
        • spanはサブリストのタイトル
        • aspanは、どちらも空にはできない(imgなどを含むことはできるが、テキストとして処理した際に必ず1文字以上となるように、titleやaltを付与すること)
      • aを含むliは、追加で最大1個のolを含むことができる
      • spanを含むliは、追加で必ず1個のolを含む(spanをleaf要素にすることはできない)

epubcheckを使おう

原則として、EPUB公開前にはepubcheckによるチェックを通しておくと安全です。epubcheckを通過したからと言って100%正当なEPUBであることが保証される訳ではありませんが、通常制作していく中で埋め込んでしまうEPUB仕様違反(主に凡ミス)の99%くらいは発見できると思います。

java -jar epubcheck.jar target.epub

よくある間違い

1. ネストしたリストに、spanによるheadingがついていない

以下のようなケースです。問題の行を👈で示しました。

<ol>
  <li><a href="p001.xhtml">第1章</a></li>
  <li> 👈
    <ol> 👈
      <li><a href="p002-1.xhtml">第2章その1</a></li>
      <li><a href="p002-2.xhtml">第2章その2</a></li>
    </ol>
  </li>
</ol>

liは必ず最初にaまたはspanを含む必要があり、olはこれらに続く形でのみ記述できます。つまり、タイトルのつかないサブリストは許容されません。

epubcheckでは以下のようなエラーが指摘されます。

ERROR(RSC-005): …: ファイル解析時のエラー ‘要素 “ol” はまだここには書けません. ; ここに書かれるべきものは 要素 “a” または “span” です.’.

修正すると、以下のようになります。第2章の扉がある場合などは、spanの代わりにaを利用してももちろん構いません。

<ol>
  <li><a href="p001.xhtml">第1章</a></li>
  <li>
    <span>第2章</span>
    <ol>
      <li><a href="p002-1.xhtml">第2章その1</a></li>
      <li><a href="p002-2.xhtml">第2章その2</a></li>
    </ol>
  </li>
</ol>

2. href属性のないa要素がある

以下のようなケースです。

<ol>
  <li><a href="p001.xhtml">第1章</a></li>
  <li><a>セクション区切り</a></li> 👈
  <li><a href="p002.xhtml">第2章</a></li>
</ol>

A child a element describes the target that the link points to,

などと書かれているように、aはリンク(目次項目)のために利用する必要があります。spanの代わりに利用してはいけません。

この違反は、epubcheck 4.0.2では特にエラーが出力されないので注意が必要です。

修正するには、該当のaを含むliを削除するのが最も簡単です。もし後続の項目がセクションとしてまとめられるなら、spanに後続するサブリストとして利用することも検討してください。ブラウザで見た際の間隔を空けたいのなら、CSSを使いましょう。

<ol>
  <li><a href="p001.xhtml">第1章</a></li>
  <li><a href="p002.xhtml">第2章</a></li>
</ol>

3. aspanを含まないli要素がある

以下のようなケースです。

<ol>
  <li><a href="p001.xhtml">第1章</a></li>
  <li>-----</li> 👈
  <li><a href="p002.xhtml">第2章</a></li>
</ol>

liは必ずaまたはspanを1つ含む必要があり、区切り線のような用途に使うことは禁止されています。

epubcheckでは以下のエラーが報告されます。

ERROR(RSC-005): …: ファイル解析時のエラー ‘テキストはここには書けません. ; ここに書かれるべきものは 要素 “a” または “span” です.’.

修正するには、該当のliを削除します。

<ol>
  <li><a href="p001.xhtml">第1章</a></li>
  <li><a href="p002.xhtml">第2章</a></li>
</ol>

4. nav配下に余計な要素がある

以下のようなケースです。

<nav epub:type="toc">
  <p>目次です。</p> 👈
  <ol>
    <li><a href="p001.xhtml">第1章</a></li>
    <li><a href="p002.xhtml">第2章</a></li>
  </ol>
</nav>

nav直下には0個または1個のheading要素と必ず1個のol要素を置くことを必要とされ、それ以外は許可されません。

epubcheckでは以下のエラーが報告されます。

ERROR(RSC-005): ..: ファイル解析時のエラー ‘要素 “p” をここに書いてはいけません. ; ここに書かれるべきものは 要素 “ol” です.’.

修正するには、該当のpを削除するか、navの外に置くのが簡単です。

<p>目次です。</p>
<nav epub:type="toc">
  <ol>
    <li><a href="p001.xhtml">第1章</a></li>
    <li><a href="p002.xhtml">第2章</a></li>
  </ol>
</nav>

5. HTMLが壊れている

以下のようなケースです。

<ol>
  <li><a href="p001.xhtml">第1章</a></li>
  ------- 👈
  <li><a href="p002.xhtml">第2章</a></li>
  <ol> 👈
    <li><a href="p002-1.xhtml">第2章その1</a></li> 👈
  </ol> 👈
</ol>

どうしてこうなった、感が満載ですが、当然ながらol直下にli以外を書いてはいけません。

HTMLとしてli以外にscripttemplateも含むことができますが、EPUB Navigation Documentとしてはliのみが許可されます。テキストやolはHTMLでも当然NGです。

関連記事

源ノ明朝フォントの縦書きはSafariで半角数字が正立するので気をつけよう

$
0
0

源ノ明朝フォント、使いやすくて良いですよね。文字数、デザイン、ライセンスのバランスが良くて汎用性が高いです。超縦書でも、以前はIPAex明朝のカスタムフォントを同梱していましたが、現在では源ノ明朝のカスタム版に変更しています。

そんな源ノ明朝ですが、縦書きで使うと、Safari10.1.1で開いた際に半角数字が正立してしまうのでご注意ください。

左が源ノ明朝、右が通常のフォント

詳しくはCSS Writing Modes 3UAX#50を参照いただくとして、本来英数字はUAX#50のプロパティがRなので、text-orientation: uprightを指定しない限り横向きになる(=横に寝る)はずであり、他のブラウザやSafariでも他のフォントでは横になります。

色々試してみたところ、フォントにloclテーブルがありそれで置換されたグリフは、問答無用で正立してしまっているようでした。ちなみに源ノゴシックでは英数字のlocl置換は入っていないので、この問題は発生しません。

WebKitにバグレポ投げたらradarにインポートはされたので、多分そのうちに直るとは思いますが、しばらくはlocl外したビルドを作るなりfont-feature-settings: "locl" 0するなりの工夫が必要そうです。

関連記事(フォント)

Viewing all 101 articles
Browse latest View live