読者です 読者をやめる 読者になる 読者になる

UUUM攻殻機動隊

UUUMのエンジニアによる技術ブログです

正規表現エンジン入門 - 正規表現の概念

おはこんばんちは!! 尾藤 a.k.a. BTO です。

みなさん正規表現使ってますでしょうか? 正規表現はプログラムを書く時にも使いますが、エディタや grep で検索するときなど文字列検索をするときには非常にお世話になっていると思います。 正規表現を書くことはあっても、正規表現がどのように処理されているのかを知らない人も多いのではないでしょうか。

今回、正規表現エンジンがどのような仕組みでできているのかを社内勉強会でやったので、その内容をまとめてみます。

正規表現技術入門

正規表現エンジンに関しては、正規表現技術入門というとてもわかりやすい本が出ています。

正規表現技術入門 ――最新エンジン実装と理論的背景 (WEB+DB PRESS plus)
新屋 良磨 鈴木 勇介 高田 謙
技術評論社
売り上げランキング: 117,162

実は本ブログで書いた内容も、かなりの部分はこの本から得た知識になっています。 正規表現エンジンに興味のある方には、ぜひ読んでみてください!!

チョムスキー階層

ノーム・チョムスキーという著名な言語学者がいます(ご存命です)。 チョムスキーは、形式言語を定義する形式文法を階層化しました。 これをチョムスキー階層と呼びます。

チョムスキー階層は次の4つに分かれます。

  • 制限のない文法
  • 文脈依存文法
  • 文脈自由文法
  • 正規文法

上の階層ほど、言語としての自由度が高く、計算機で処理するのが難しくなります。 名前から想像できると思いますが、正規表現はこのチョムスキー階層で言うところの正規文法にあたります。

正規表現とは

では、正規表現とは一体何なんでしょうか?

先程のチョムスキー階層で出てきた正規文法で定義された言語が正規言語です。 そしてこの正規言語を表現するための記法が正規表現です。

このように元々は正規言語を表すために作られた正規表現ですが、みなさんご存知のように現在では文字列のパターンマッチングのために使用されるようになっています。

正規表現による文字列の検索はとても便利だったので、いろんなツールに導入されました。 より便利にするために様々な機能拡張がおこなわれてきました。 そのため現在では本来の正規表現の定義を超えた機能を実装とかも出てきています。

世界初の正規表現エンジン

世界初の正規表現エンジンは、QEDというテキストエディタの文字列検索機能として実装されました。 この世界初の正規表現エンジンを実装したのがケン・トンプソン(ご存命です)です。

ケン・トンプソンは知る人ぞ知る超有名な計算機科学者で、業績は多岐に渡ります。 彼の業績には次のようなものがあります。

  • UNIX
  • C言語
  • 正規表現エンジン
  • ed(ラインエディタ)
  • UTF-8
  • Plan 9
  • golang

ちょっとやばいですね... レジェンド過ぎます...

正規表現の限界

とても便利な正規表現ですが、チョムスキー階層で最下層に分類されているところからも分かる通り、その能力には限界があります。良く出てくる例としては、正規表現は括弧の対応を処理することができません

つまり、次のような文字列、左に現れた開き括弧と同じ数の閉じ括弧が右側に現れるような文字列を処理することができません。

((( ... )))  # このような文字列は正規表現で処理できない

括弧の対応が組み込まれている文字列はいろいろとあります。

  • 四則演算
  • プログラミング言語
  • HTML, CSS のような形式言語

これらの言語を処理する能力は正規表現にはありません(もちろん部分文字列を処理する能力は当然あります)。

メールアドレスは正規表現で処理できるか?

よくある話として、メールアドレスは正規表現でできるかどうかという話があります。 結論から言うと、本来の定義での正規表現でメƒールアドレスを表現することはできません。

メールアドレスはネストしたコメントを持つことができます。 上記の ((( ... ))) のような正規表現で処理できない構造は、ネストした構造を持つために使われるものですので、正規表現では表現できないことになります。

ただし、現在の正規表現には様々な拡張が行われていて本来の正規表現の能力を超える処理を提供しているものがあります。 具体的には、再帰の機能を持った正規表現エンジンでは、ネストしたコメントを表現することができます。 Perl や Ruby で使われている正規表現は再帰の機能を持っているので、メールアドレスを正規表現で処理することができます。

正規表現の定義

ちょっと前置きが長いですが、正規表現の話を進めましょう。 そのために、まずは正規表現を定義します。 正規表現の定義は次のようになります。 このでの定義は文字列処理に使われる正規表現について考えています。

  • 文字は正規表現である
  • X, Y が正規表現とする
    • XY は正規表現である(連接)
    • X | Y は正規表現である(選択)
    • X* は正規表現である(繰り返し)

本来の正規表現では、空集合も受け付けるので、空集合は正規表現であるという定義が入りますが、ここでは省略しています。 なぜなら文字列処理を目的とする場合は、空文字(空集合)を処理する必要がないからです。 実際、文字列処理の正規表現では空文字を表現する記法がありません。

正規表現3つの基本演算

上記の正規表現の定義を見ると、正規表現は基本的な演算の組み合わせで構成されていることがわかります。 その基本的な演算は次の3つになります。

  • 連接
  • 選択
  • 繰り返し

正規表現はとても便利なものなので、様々な拡張機能が実装されてきましたが、この基本演算の組み合わせで表現することができます。

一部上記の基本演算では表現できない機能もあります。 メールアドレスのところで出てきた再帰がそれにあたります。 この様な機能は便利なものではあるのですが、同時に正規表現の定義から外れてしまっているので数理的な意味を失うことにもなってしまいます。

連接

連接は非常に単純で、正規表現を繋げたものになります。 例えば次のような正規表現が連接になります。

abcdef

選択

選択は複数の正規表現から選択する機能です。 複数の正規表現を | で区切ります。 例えば次のような正規表現は、a, b, ab のいずれかの文字列を表します。

a|b|ab

繰り返し

繰り返しは、正規表現の0回以上の繰り返しを表します。 繰り返しの記号は * です。 例えば次のような正規表現は、aの0回以上の繰り返しを表します。

a*

拡張演算

上記の3つの演算に加えて、現在の正規表現では様々な拡張機能を提供しています。 機能はいろいろありますが、どれも3つの基本演算の組み合わせで表現することが可能です。

全てではありませんが、拡張された演算には次のようなものがあります。

  • プラス演算: 1回以上の繰り返し
  • 疑問符演算: 0回か1回
  • 範囲量指定: n から m回の繰り返し

正規表現エンジンの実装方法

では、正規表現エンジンの実装をみていきましょう。 正規表現エンジンの実装方法は大きく分けると2種類あります。

  • DFA型
  • VM型

では、実装の説明を... と思ったのですが、長くなってきたので次回以降で。

まとめ

正規表現エンジン入門と言いつつ、エンジンの説明までたどり着きませんでした... ですが、正規表現の基本的な概念は理解していただけたのではないかと思います。 正規表現エンジンの実装には、正規表現自体への理解は必要不可欠です。

次は有限オートマトンと呼ばれる DFA での実装について書いていきます。

Elasticsearchの基本的な使い方と弊社サービスでの利用

UUUMに入って2か月のkitabatakeです。 任される仕事が多くなってきて、面白くなってきたと思う一方、ちゃんと捌ききれるかどうかのせめぎ合いの日々です。

弊社のシステムでも使っている、Elasticsearch の基本的な使い方と、弊社内での利用方法など書いてみたいと思います。

Elasticsearchとは?

全文検索エンジン

大量にあるドキュメントデータの中から、目的のワードを含むドキュメントデータを検索する。 検索するということに特化したミドルウェア

RDBMSとの違い

「RDBMS」は、検索条件にマッチするデータ取得できる。

Elasticsearchのような「全文検索エンジン」は、「検索条件との関係性/関連性が高いデータを抽出して返す」という特徴がある。

RDBMSにも全文検索機能が搭載されているものもあるが、検索機能に最適化されている分、パフォーマンスや機能性は一般的に「全文検索エンジン」の方が優れている。

データの登録から関連性の紐付けについての概要

下記資料のサザエさんのところにかなり解りやすく説明されています

ElasticSearch入門 // Speaker Deck

実際に試してみる

弊社のクリエイターポータルサイト「CREAS」では素材の検索にElasticsearchを使っています。 同じようなケースの想定で、解りやすくミニマムのデータ構造で試してみます。 Elasticsearchがインストール済みの想定で進めます。

簡易素材データ

  • ID
  • name
  • description

Indexの作成

Indexとは?

データを保存する場所。 RDBMSのdatabaseのようなもの

indexの一覧を確認するコマンド

curl -XGET 'localhost:9200/_aliases?pretty'

---

{}

?pretty はレスポンスのjsonを見やすく表示させるオプションです。 まだ何もしていないので、空のレスポンスが返ってきます。

indexを作成します。今回はindex名を material_test とします。

curl -XPOST 'http://localhost:9200/material_test'

でindex一覧を確認

curl -XGET 'localhost:9200/_aliases?pretty'
---
{
  "material_test" : {
    "aliases" : { }
  }
}

作れました。

Mapping

外部データをElasticsearch上でどのようなスキーマとして表現するか定義することをmappingと呼びます。 (Elasticsearchチュートリアル - 不可視点より。参考: Mapping のイメージ)

マッピングの確認

curl -XGET 'localhost:9200/material_test/_mapping?pretty'
---
{
  "material_test" : {
    "mappings" : { }
  }
}

Elasticsearchはデータを投入すると、自動的にマッピングを作成してくれるので、まずはそれを試してみます。 データを投入します。

curl -XPUT 'localhost:9200/material_test/material/1' -d '{
    "title" : "アラビアンジュエル",
    "description" : "アラビアンナイトな雰囲気の怪しい楽曲です。 ゲームなどの、砂漠の街のイメージなどにおすすめです。"
}'

で、Mappingの確認

curl -XGET 'localhost:9200/material_test/_mapping?pretty'
---
{
  "material_test" : {
    "mappings" : {
      "material" : {
        "properties" : {
          "description" : {
            "type" : "string"
          },
          "title" : {
            "type" : "string"
          }
        }
      }
    }
  }
}

name, descriptionのtypeがstringと定義されました。 これでデータが検索できる状態になったので、 WEB UIから検索等のできるプラグイン

github.com

を使って試してみよう。

インストール後に、 http://localhost:9200/_plugin/inquisitor/#/queries にアクセスします。

Queriesタブで検索クエリーの発行、Analyzersタブで文章の解析結果が確認できます。

Queries画面

f:id:sissoko:20161122090137p:plain

Analyzers画面

f:id:sissoko:20161122090157p:plain

自動的に生成されたmappingでは日本語をうまく解析できていないですね。

いい感じに検索できるようにする

日本語対応 analyzer kuromoji(http://www.atilika.org/) というものがあり、これを設定することで、日本語の検索がいい感じになります。

mappingを手動で設定するコマンド

curl -XPUT 'localhost:9200/material_test' -d '
{
  "settings": {
    "analysis": {
      "filter": {
        "pos_filter": {type: "kuromoji_part_of_speech", stoptags: ["助詞-格助詞-一般", "助詞-終助詞"]},
        "greek_lowercase_filter": {type: "lowercase", language: "greek"}
      },
      "tokenizer": {
        "kuromoji": {
          "type": "kuromoji_tokenizer"
        },
        "ngram_tokenizer": {
          "type": "nGram",
          "min_gram": "2",
          "max_gram": "3",
          "token_chars": [
            "letter",
            "digit"
          ]
        }
      },
      "analyzer": {
        "kuromoji_analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji_tokenizer",
          "filter": [
            "kuromoji_baseform",
            "pos_filter",
            "greek_lowercase_filter",
            "cjk_width"
          ]
        },
        "ngram_analyzer": {
          "tokenizer": "ngram_tokenizer"
        }
      }
    }
  },
  "mappings" : {
    "material" : {
      "properties" : {
        "name" : {
          "type" : "string",
          "analyzer": "kuromoji_analyzer"
        },
        "description" : {
          "type" : "string",
          "analyzer": "kuromoji_analyzer"
        }
      }
    }
  }
}'

急に複雑になってしまったので、細かく説明していきます。

まず Analyzerとは?

Analyzer = Tokenizer + Filter です。

Tokenizer?

文字を分割するもの

  • ngram_tokenizer:

    最小と最大の文字と対象にする文字の種類を設定し、分割する

  • kuromoji_tokenizer:

    日本語に対応したプラグインのtokenizer

Filter?

分割した文字を整形したり、フィルタリングするもの

  • kuromoji_baseform

    動詞と形容詞を原型に戻す。

  • pos_filter

    特定の品詞を除外し、マッチ精度を高める

  • greek_lowercase_filter

    英字を小文字化する

  • cjk_width

    全角英数字を半角に直し、半角カタカナを全角に直すなど、漢字圏の文字の全半角を整え、マッチしやすくする

設定を確認する

Analyzer, Tokenizer, Filterが何かわかったところで、上の設定のjsonを見ると、 settingsのところに、tokenizer, filter が設定されていて、それをanalyzerに設定している。 mappingsのところで、各フィールドにanalyzerを指定している、というのが解ると思います。

コマンド実行後にmappingを確認

curl -XGET 'localhost:9200/material_test/_mapping?pretty'
---
{
  "material_test" : {
    "mappings" : {
      "material" : {
        "properties" : {
          "description" : {
            "type" : "string",
            "analyzer" : "kuromoji_analyzer"
          },
          "name" : {
            "type" : "string",
            "analyzer" : "kuromoji_analyzer"
          }
        }
      }
    }
  }
}

文章の解析がいい感じになりました。

f:id:sissoko:20161122091408p:plain

以上が基本的な使い方でした。

弊社サービス「CREAS」ではどのように使っているか

creators.uuum.jp

CREASではクリエイター向けに様々な動画作成向けの素材を提供しています。

フレームワークはSymfonyを使っていて、

SymfonyでElasticsearchを簡単に扱えるFOSElasticaBundleの紹介 - UUUM攻殻機動隊

同ブログのnazo様の記事にSymfonyからElasticsearchをどのように使っているかが解りやすくまとめられています。

app/config/elastica.yml

に 上で話していた、analyzerなどの設定が書かれているので、見てみましょう。

settings部分抜粋

settings:
    index:
        analysis:
            tokenizer:
                kuromoji:
                    type: kuromoji_tokenizer
            analyzer:
                kuromoji_analizer:
                    type: custom
                    tokenizer: kuromoji
                    filter:
                        - kuromoji_baseform
                        - lowercase

kuromoji_analyzerが設定されています。

materialのmapping部分抜粋

material:
mappings:
id:
    type: integer
    index: not_analyzed
download_count:
    type: integer
    index: not_analyzed
type:
    type: string
    index: not_analyzed
material_categories:
    type: nested
    properties:
        id:
            type: integer
            index: not_analyzed
material_tags:
    type: nested
    properties:
        id:
            type: integer
            index: not_analyzed
        name:
            type: string
            index: not_analyzed
name:
    type: string
    index: analyzed
    analyzer: kuromoji_analizer
description:
    type: string
    index: analyzed
    analyzer: kuromoji_analizer
artist_name:
    type: string
    index: analyzed
    analyzer: kuromoji_analizer
artist_name_not_analyzed:
    type: string
    index: not_analyzed
    property_path: artist_name
artist_katakana:
    type: string
    index: not_analyzed
is_downloadable:
    type: boolean
    index: not_analyzed

name, descriptionなどはkuromoji_analyzerが設定されています。

id_downloadableなどのフラグは解析してもしょうがないので、 index: not_analzyed の指定がされています。 これにより、単純なフラグのon/offでの検索に対応することができます。

クエリ発行箇所

音源素材のtypeと、検索ワードから関連するデータを取得するメソッドの内容を一部抜粋

/**
     * キーワード検索結果でページネーション
     *
     * @param string $type MaterialType
     * @param string $word 検索キーワード
     * @return FOS\ElasticaBundle\Paginator\TransformedPaginatorAdapter
     */
    public function findPaginatedByKeywords($type, $word)
    {

...

        [
            'match' => [
   'is_downloadable' => true,
            ]
        ],
        [
            'query_string' => [
   'fields' => ['name', 'description', 'artist_name'],
   'query' => '"' . $word . '"',
            ],
        ],
        [
            'match' => [
   'type' => $type,
            ]
        ],

...

        return $this->elastica->createPaginatorAdapter('Material', $query);
    }

ダウンロードが可能(is_downloadable)で、対象の音源素材の種別から、 name, description, artist_name と検索ワードの関連があるデータを取得しているということが、わかると思います。

まとめ

kuromojiのfilterやtokenizerがよくできていたり、各種プラグインがよくできているので、触っているだけで結構面白くて、全文検索に対する理解も深まるのでオススメです!

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

CSRF(クロスサイトリクエストフォージェリ)

エンジニアのspin13です. しばらくの間,地球そのものが敵となって花粉に苦しめられていました.

今回は クロスサイトリクエストフォージェリ(以後CSRF)について書きます.

続きを読む

GoとNode.jsで作った簡易サーバーのパフォーマンスを比較してみた

エンジニアのタナカです。
UUUMではiOSアプリを開発をしています。

アプリを開発するにあたって、バックエンドに何を使えばいいか悩んだりしませんか? バックエンドを決めるには、様々な側面から考える必要があると思いますが、今回はパフォーマンスだけに着目して検討してみました。

ということでGoとNode.jsで簡易WEBサーバーを作って比較してみたいと思います。おまけでRubyも比較してみました。
パフォーマンスで考えた場合にRubyを選ぶことはあまりないとは思いますが、参考までに。

検証環境

  • Go go1.6.2 darwin/amd64
  • Node.js v5.10.1
  • Ruby ruby 2.2.4p230

※GOMAXPROCSは設定していません(マルチコアを使用できるGoが有利な環境)

検証コード

UNIX TIMEを返すだけの簡単なサーバーを用意しました。

Go

標準パッケージのみで作成

package main

import (
    "fmt"
    "net/http"
    "time"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "%d", time.Now().Unix())
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

Node.js

Expressは使わず標準パッケージで

var http = require('http');

var server = http.createServer(function (request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end(String(Math.floor( (new Date()).getTime() / 1000 )));
});

server.listen(8080);

Ruby

こちらもwebrick等は使わずに

require 'socket'
require 'time'

server = TCPServer.new 8080
loop do
  client = server.accept
  client.gets
  client.puts "HTTP/1.0 200 OK"
  client.puts "Content-Type: text/plain"
  client.puts
  client.puts Time.now.to_i
  client.close
end

比較1

Apache付属のabコマンドを使用しました。 今回は、同時100接続のベンチマークを取りたいと思います。そのため以下のオプションを指定しました。

ab -n 100 -c 100 http://127.0.0.1:8080/

結果1

ローカルで動かしたので正確な値ではありませんが、以下の結果になりました。

Go

Concurrency Level:      100
Time taken for tests:   0.008 seconds
Complete requests:      100
Failed requests:        0
Total transferred:      12700 bytes
HTML transferred:       1000 bytes
Requests per second:    12584.95 [#/sec] (mean)
Time per request:       7.946 [ms] (mean)
Time per request:       0.079 [ms] (mean, across all concurrent requests)
Transfer rate:          1560.83 [Kbytes/sec] received

Node.js

Concurrency Level:      100
Time taken for tests:   0.046 seconds
Complete requests:      100
Failed requests:        0
Total transferred:      11100 bytes
HTML transferred:       1000 bytes
Requests per second:    2161.69 [#/sec] (mean)
Time per request:       46.260 [ms] (mean)
Time per request:       0.463 [ms] (mean, across all concurrent requests)
Transfer rate:          234.32 [Kbytes/sec] received

Ruby

Concurrency Level:      100
Time taken for tests:   0.007 seconds
Complete requests:      100
Failed requests:        0
Total transferred:      5300 bytes
HTML transferred:       1100 bytes
Requests per second:    14341.03 [#/sec] (mean)
Time per request:       6.973 [ms] (mean)
Time per request:       0.070 [ms] (mean, across all concurrent requests)
Transfer rate:          742.26 [Kbytes/sec] received

「Requests per second」が1秒間に処理されたリクエスト数なので、この数値が大きい方がパフォーマンスがよいことになります。
意外にも一番よいスコアーだったのはRubyでした。 僅差でGo、Node.jsは1桁低いスコアーでした。

今回用意した検証コードがI/OよりCPUリソースが必要だったのか、シングルスレッドのNode.jsにとっては不利なものだったのかもしれません。

同時100接続ではRubyのスコアーがよかったですが、同時2000接続では、Rubyは応答できませんでした。
また、Goのスコアー7329.66に対してNode.jsのスコアーは3914.49と差が小さくなっていました。
Node.jsはアクセスが多い場合に力を発揮するようです。

比較2

比較1で使用したabコマンドでの同時接続数を100づつ増やして上限を確認してみました。

結果2

Go

同時2300までOK、2400でエラー

Node.js

同時2100までOK、2200でエラー

Ruby

同時120までOK、130でエラー

同時接続数はGoが一番よい結果が出ました。
Node.jsもGoに続くよい結果でした。 Rubyが少なすぎる気がしたのでwebrickでも試してみましたが、同じような結果でした。

まとめ

応答速度、同時アクセス数とも、Goのパフォーマンスが優れています。 通信回数の多いゲーム等を開発する場合は、バックエンドにGoを使うとよいかもしれません。

あと、処理内容にもよると思いますが、同時100アクセスくらいならRubyで十分かもしれません。

Ansibleことはじめ

こんにちは、エンジニアのナカハシです。

UUUMのインフラ構築はAnsibleで構築することが恒例になっており、絶賛開発中のRailsアプリも当然Ansibleでインフラを記述しています。

つまり、AnsibleのPlayBookを読めないと既存のインフラもさっぱり理解できなくなるわけで、Webアプリの実装ばかりやっている私としてもさすがにヤバいのではないかと思い、改めてAnsibleに入門してみることにしました。

続きを読む

SVGの基本的な仕組みについて

どうも、えんじにあのやまぐちです。

先日UUUMで社内運動会が行われ、攻殻機動隊も数名参加しました。 未だに筋肉痛がとれず日常生活に支障がでていますが、普段動かない仕事なだけに久しぶりにスポーツをして楽しかったです。 ちなみに我らのCTOは女性参加型のドッジボールで全力投球をかましていました。

さて、今回の記事ですが、SVGについて書かせていただこうと思います。
3年前ぐらいからよく目にしていたのですが、閲覧環境が整っていない感があったので取り敢えずスルーしていました。しかし、弊社のWebサイトでもSVGが使用されていたのを発見したので、この機会にどういう仕組なのか少し調べてみました。

SVGとは

SVGとはScalable Vector Graphicsの略です。一般的な画像はJPGやPNGといったラスタ形式(ビットマップ)ですが、SVGはベクタ形式で表現します。
ベクタ形式の特徴はアンカーと呼ばれる座標を複数指定し、そのアンカーを線で繋いだり、囲まれた範囲内を塗ることで描画します。

ベクタ形式とラスタ形式

ベクタ形式とラスタ形式の最大の特徴はリサイズによる画像の劣化があげられます。 ラスタ形式はドットが集合した画像なのでリサイズ時に画像が劣化する場合がありますが、ベクタ形式では先ほど説明したように座標を複数の点で指定して描画するため、リサイズ時に劣化がほとんどありません。

形式 利点
ベクタ 直線や曲線などで構成された図形
ラスタ 写真や風景など複雑なグラフィック

SVGはXMLベースの文書形式で作成されているため、テキストエディタで編集することが可能です。画像と違いテキスト形式なのでバージョン管理システムとの相性が良いとも言われています。 しかし、手軽に編集ができるため容易に改ざんすることも可能です。そのためセキュリティには気をつけなければなりません。

ブラウザのサポート状況

f:id:tky_ymgc:20161018195144p:plain

引用元: http://caniuse.com/#feat=svg-html5

モダンブラウザにはほぼ対応済みです。しかしIE8など一部のブラウザで対応できていないのでフォールバックする必要はあるかと思います。 例えば、ModernizrといったJSライブラリを用いてブラウザのSVGサポート状況を判別し、サポート外であった場合にPNG画像などで代替するといったことが必要となります。

Modernizr

HTML5やCSS3などの新しいWebの標準がWebブラウザに実装されているか、汎用的なインタフェースを通じて確認することができるJavaScriptライブラリ

SVGとCanvas

CanvasはHTNL5でグラフィックを表現するための要素です。特徴としてはラスタ形式(ビットマップ)で描画され、画像を別形式で保存しやすいことです。ここがSVGとの最大の違いです。
SVGとCanvasはそれぞれ適しているケースで使い分けると良いです。

SVGが適しているケース

  • 円や四角形などの図形の組み合わせで構成されている
  • ベクタ形式なので拡大、縮小で図形の劣化が少ない方が良い
  • 外部ファイルとして受け渡しを行い、別のHTML上でも表示させたい

Canvasの方が適しているケース

  • ビットマップ形式なのでピクセル単位での細かい描画を行いたい
  • 描画した画像を別形式の画像(PNGなど)で保存したい

SVGの埋め込み方法

SVGにはいくつかの埋め込み方法があります。 通常の画像ファイルのようにHTMLのimgタグやCSSのbackground-imageで読み込む方法や、SVG要素として埋め込む方法、外部メディアとして読み込む方法があります。

画像としてHTMLに埋め込む

<img src="assets/svg/image.svg" alt="" width="100" height="100">

画像としてCSSに埋め込む

.svg {
    background-image: url(assets/svg/image.svg);
}

外部メディアとして埋め込む

<object data="assets/svg/image.svg" width="300" height="300" codetype=“image/svg+xml"></object>

要素として埋め込む

<svg width="300" height="300">
    <line x1="0" y1="0" x2="300" y2="300" stroke="black" stroke-width="1" />
</svg>

基本的な図形

テキスト

<svg width="300px" height="300px">
    <text x="100" y="100">Hello World</text>
</svg>

テキストは<text>タグを使用します。左上が起点となり、X座標とY座標の指定した位置からテキストが描画されます。

直線

<svg width="300px" height="300px">
    <line x1="0" y1="0" x2="100" y2="100" stroke="black" stroke-width="1" />
</svg>

直線は<line>タグを使用します。X座標1、Y座標1からX座標2,Y座標2までを直線で結びます。属性にstrokeを記述することで線の色や太さを指定することができます。

四角形

<svg width="300px" height="300px">
    <rect x="10" y="10" width="100" height="100" stroke="black" stroke-width="1" fill="red" />
</svg>

四角形は<rect>タグを使用します。左上が起点となり、X座標とY座標の指定した位置から四角形が描画されます。widthとheightを使用して大きさを指定します。

円形

<svg width="300px" height="300px">
    <circle cx="100" cy="100" r="50" stroke="black" stroke-width="1" fill="blue" />
</svg>

円形は<circle>タグを使用します。CX座標とCY座標で円の中心地点を指定し、rで半径の大きさを指定します。

パス図形

<svg width="300px" height="300px">
  <path stroke="black" stroke-width="1" d="M20,70 h60l-30,60z"/>
</svg>

パス図形は<path>タグを使用します。任意の図形を描画することができるので自由度は高いですが、上記の図形よりも少し複雑になります。 パス図形ではd属性(パスデータ)を指定することで曲線を描画することができます。d属性にはパスコマンドをカンマやスペース区切りで複数記述し、前から順に実行していき、曲線で囲まれた領域がパス図形となります。

以下は基本的なパスコマンドの一覧です。

基本的なパスコマンド

コマンド 操作
M, m 初期位置
L, l 直線を引く
H, h 水平線を引く
V, v 垂直線を引く
C, c \ S, s \ Q, q \ T, t 曲線を引く
A, a 円弧を引く
Z, z 線を閉じる

※大文字は絶対位置を指定、小文字は相対位置を指定する

【Rails】Rubyテンプレートで axlsx を出力する方法

Rails Ruby

ハローこんにちは、新人エンジニアのハトネコエです。

2016年10月22日発売の 『WEB+DB PRESS Vol.95』
UUUM のエンジニア達が書いた特集『試して学ぶHTTP』が載ります。よろしくお願いします。

WEB+DB PRESS Vol.95

WEB+DB PRESS Vol.95

  • 作者: 小出淳子,黒澤剛志,牧大輔,横江亮佑,山口貴也,尾藤正人,佐藤琢哉,中橋研太郎,田中慎司,小西裕介,伊藤直也,稲富駿,前島真一,長野雅広,山際康貴,のざきひろふみ,うらがみ,岡林大,遠藤雅伸,ひげぽん,海野弘成,はまちや2,竹原,大場寧子,大場光一郎,野々下裕子,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2016/10/22
  • メディア: 大型本
  • この商品を含むブログを見る

Rubyテンプレート

Rails4になってcsvが出力しやすくなった - Qiita

ここの記事にあるように、view テンプレートとして .ruby ファイルが使えるようになっています。
長くなりがちな CSV 出力の処理を controller 内でなく別ファイルに書けるので controller がすっきりしますね。

Excel 出力もこんなふうにしたい

Excel で表データを扱うことを前提にすると、CSV では、文字コードとか先頭に0を詰める出力がつらい。
CSV でなく Excel 用の .xlsx 形式で書き出したいよねーというわけで、
お世話になっているのが Axlsx という gem。

これで上記のような方法で controller と出力処理を分離しつつエクスポートする方法を探りました。

続きを読む