【社内勉強会】Web開発者に知ってほしい「文字コード」の話（2017/04/19)



# 0. はじめに

--------------
## 対象者

* Webシステムを開発している人
    - Java Servlet
    - HTML5

--------------
## 伝えたいこと
* 文字コードの概要
* 文字コードの指定箇所
* １文字なのに２文字と認識される場合がある

--------------
## 参考図書

[プログラマのための文字コード技術入門](https://gihyo.jp/dp/ebook/2014/978-4-7741-7087-9)

![参考図書.jpg](https://qiita-image-store.s3.amazonaws.com/0/37903/f2d0f325-f876-20e9-846e-121b33223ee6.jpeg)

このスライドでは、この本の文章や図を引用している。

--------------
## 目次
1. 文字コードの概要
 * 代表的な文字コードの特徴
 * ASCIIとASCII互換の文字コード
 * UTF-8を推奨
 * 文字コードの自動判別
2. Webシステムにおける文字コードの指定個所
3. 付録



--------------
# 1. 代表的な文字コードの特徴

--------------
## 文字コードとは？

>文字集合を定義し、その集合の各文字に対応するビット組み合わせを一意に定めたものが文字コードです。

参考図書より引用

たとえば、「[A]という文字は[1000001]というビット組み合わせにする」というルールのこと。


--------------
## 代表的な文字コード

* Shift_JIS
* EUC-JP
* UTF-8


--------------
### EUC-JP

日本語を表現する文字コードの一つ。
**E**xtended **U**NIX **C**ode Packed Format for **J**apanese の略。
Unix系OSで広く使われている。
1985年にAT&T社が定めた。

最大2バイト。

最近のUnix系OSのデフォルト文字コードはUTF-8。

--------------
### Shift_JIS

日本語を表現する文字コードの一つ。
1982年に誕生した。

各ベンダーの亜種が存在する。
（「Windows-31J」など。波ダッシュ問題の原因。後述参照）

最大2バイト。

--------------
### UTF-8

世界中の文字を扱えるようにした「Unicode」という文字集合を、8bit単位で表現した文字コード。
たとえば、アクセント記号([é])はShift_JISでは表現できないが、UTF-8ならば表現できる。

Unicodeは1993年制定された。
“UCS Transformation Format 8”、または “Unicode Transformation Format-8” の略。

最大4バイト。



--------------
## ASCIIとは？

American Standard Code for Information Interchangeの略。
最も基本的な文字コード。1960年代に誕生した。

7bitで128文字収録。
![ASCII.jpeg](https://qiita-image-store.s3.amazonaws.com/0/37903/6d7e1346-0d20-2901-6931-96fa048c7be7.jpeg)
0x00～0x1Fは制御文字（タブや改行など）。


--------------
## ASCII互換の文字コード

ASCII文字（半角英数字）のビット表現が、ASCIIと一致する文字コードのこと。

* ASCII互換
    - Shift_JIS
    - EUC-JP
    - UTF-8
* 非ASCII互換
    - UTF-16
    - UTF-32

[A]という文字はASCII互換では0x41、UTF-16では0x0041。
（後ほど、ビット表現を実際に確認）
半角英数字のビット表現は、Shift_JIS, EUC-JP, UTF-8で同じ。

コンパイラは一般的にASCIIを前提としているので、プログラムファイルはASCII互換の文字コードで保存すること。


--------------
## 文字コードのまとめ

| 文字コード |ASCII互換| バイト数| 収録文字|
|:-----------|:--:|:------------|:-----------|
| Shift_JIS |Yes     | 1～2|  日本語で使う文字  |
| EUC-JP |Yes    |   1～2 |  日本語で使う文字    |
| UTF-8   |Yes    |   1～4 |   世界中の文字     |
|UTF-16 |No | 2～4 |   世界中の文字   |



--------------
## 文字コードはUTF-8一択

【メリット】

* 文字範囲が広い。どの国の文字も表示できる。
    - 多言語化対応に必須
    - ただしフォントが対応していることが条件
* ASCII互換

【例外】

* Excel2013以前で開くこと前提のCSVファイル
    - Excel2016でUTF-8に対応しました！！
    
https://www.marguerite.jp/Nihongo/WWW/RefHTML/HTML5/WhyUTF8.html
https://peta.okechan.net/blog/archives/842/comment-page-1
https://www.ka-net.org/blog/?p=7764

--------------
### UTF-8はBOMなしがベター

BOM(Byte Order Mark)は、UTF-16, UTF-32でバイトの並び順を示すもの。
UTF-8にBOMは本来関係ないが、「UTF-8」であることを示すマークとして使われる。

BOM付きUTF-8は、ファイルの先頭に3バイト（「EF BB BF」）が付与される。

ファイルの先頭にBOMが付くことを想定していない場合、予期しない結果になることがある。
UTF-8はBOMなしがベター。

http://qiita.com/MuriNishimori/items/a89fe986e28909208e30


--------------
### [補足] BOM付きUTF-8が原因のエラー

* BOM付きxmlファイルをEclipse4.6で開くと、１行目にエラーが出る。
    - エラーメッセージ：「プロローグにコンテンツは許されません。」
    - Eclipse4.6で実施
    
* BOM付きsqlファイルを、PostgreSQLのpsqlコマンドで実行すると、エラーが出る。
    - エラーメッセージ：「符号化方式"SJIS"における0xef 0xbbバイトシーケンスを持つ文字は"UTF8"符号化方式では等しくありません」
    - PostgreSQL9.6で実施
    - 実行コマンド：`psql -U postgres -d sampledb -f sample.sql`

--------------
### [補足] BOM付きUTF-8対策

* ファイル保存時の文字コード選択肢に"UTF-8"と"UTF-8N"が２つある場合（Terapadなど)は、"UTF-8N"を選択する。"UTF-8N"はBOMなし。
http://creating-homepage.com/archives/88

* BOM付きUTF-8ファイルを検索する方法

```bash
find . -type f -name '*.xml' -exec file {} + | grep "UTF-8 Unicode (with BOM)"
```

参考サイト：http://qiita.com/tailak/items/8ba2e738555f8596d3e3

* bashでBOM付きUTF-8をBOMなしにする方法(nkfコマンドがない場合）

```bash
# test.txtを上書き。ただし改行コードがCRLFからLFに変わるかもしれない。
sed -i -e '1s/^\xef\xbb\xbf//' text.txt
```
http://www.linuxask.com/questions/how-to-remove-bom-from-utf-8-using-sed より参考。

--------------
## 文字コードの自動判別

「どの文字コードで保存されたか」という情報は、テキストファイルに存在しない（UnicodeのBOMを除く）。
文字コードの判別は、「XXというビット表現は文字コードYYYにしか出現しないから、文字コードはYYYだ！」という方法。

* 確実な自動判別方法はない。
* 文字列が短いと判別に失敗しやすい。

【私が経験した勘違い】
「abc」という文字をUTF-8でファイル保存して、文字コードを判定すると[Shift_JIS]だった。




--------------
## [Try] ダンプツールでバイト表現を確認

* 「aあ」のバイト表現を確認
    - 「a」のバイト表現の違い
    - 「あ」がUTF-8では3バイト
* UTF-8のBOMを確認

| 文字コード | 「a」のバイト表現| 「あ」のバイト表現|
|:-----------|:------------|:-----------|
| Shift_JIS      | 0x61|  0x82 A0  |
| EUC-JP     |   0x61 |    0xA4 A2    |
| UTF-8       |   0x61 |    0xE3 81 82     |
|UTF-16(リトルエンディアン）  | 0x61 00 |  0x42 30   |

Unixのodコマンドで確認(Git Bashなど)。

```txt
 $ od -avtx1 -Ax Shift_JIS.txt
```

[ファイルのダウンロード](https://github.com/yuji38kwmt/learning/tree/master/data/20170419_%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89)

--------------
## [Try] 文字コードの自動判別を確認

【手順】
1. 「aA」という文字をUTF-8で保存する（test.txt)
2. test.txtの中身を「a」に変更する
3. test.txtの文字コードをUnixのfileコマンドで確認する



```txt
 $ file test.txt
```


------
# 2. 文字コードの指定方法

------
## 前提

* 全てのファイルの文字コードはUTF-8(BOMなし)


------
## 各ファイル内での文字コード宣言

* HTML
* CSS
* XML
* JSP

### 文字コード宣言が不要なファイル
* Java
* JavaScript
* properties

------
## HTMLファイル

HTML5の記述方法。

```html
<head>
  <meta charset="UTF-8">
</head>
```

>しかしながら注意点として、HTTPヘッダーによる指定の方が文書内でのmeta指定よりも優先度が高いため、ページ製作者は既にHTTPヘッダーで文字エンコーディングが指定されているかどうかについて常に頭の中に入れておくべきです。

[HTMLで文字エンコーディングを指定する](https://www.w3.org/International/questions/qa-html-encoding-declarations.ja#quickanswer) より引用

------------
## CSSファイル

```css
@charset "UTF-8";
```

>この規則はスタイルシートの最初の要素でなければならず、これより先に一切文字を記述してはいけません。

```css
@charset "UTF-8";       /* スタイルシートのエンコーディングを Unicode UTF-8 にします*/
@charset 'iso-8859-15'; /* 無効値です。正しくない引用符が使用されています */
 @charset "UTF-8";      /* 無効値です。at-規則より前に文字（空白）があります */
@charset UTF-8;         /* 無効値です。' か " がなく、文字セットは CSS の <string> ではありません */
```

[MDN @charset](https://developer.mozilla.org/ja/docs/Web/CSS/@charset) より引用


-----------
## JSP(作成中)

```jsp
<%@ page contentType="text/html;charset=UTF-8" %>
```

文字コードは、contentType属性とpageEncoding属性で指定可能。
ただ、どちらとも省略可能らしい。

http://docs.oracle.com/cd/E18355_01/web.1013/B31860-01/jspnls.htm
http://psycho-cafe.blogspot.jp/2012/01/jsppage-contenttype.html



-----------
## XML

```xml
<?xml version="1.0" encoding="UTF-8"  ?>
```




---------
## HTTP（作成中）

>It is very important to always label Web documents explicitly. HTTP 1.1 says that the default charset is ISO-8859-1. But there are too many unlabeled documents in other encodings, so browsers use the reader's preferred encoding when there is no explicit charset parameter.

[Setting the HTTP charset parameter](https://www.w3.org/International/articles/http-charset/index#charset) より引用

HTTP1.1のデフォルト文字コードはISO-8859-1。
ISO-8859-1は西欧の文字コード(Latin-1)で、日本語は対応していない。

こういった理由から、CharacterEncodingFilterでUTF-8を指定しているはず…



------
## Javaのデフォルト文字コード

>Javaでは使用している環境に応じてデフォルトのエンコーディングが決まっており、特に指定しない場合には環境毎のデフォルトエンコーディングを使ってプログラムが保存されていると考えてコンパイルを行います。

文字のエンコーディング確認方法。

```java
System.out.println(System.getProperty("file.encoding"));
```

Windows環境ではデフォルト「MS932」。
ただしEclipseから実行したときは「UTF-8」。Why?

[デフォルトエンコーディングの確認](https://www.javadrive.jp/start/encoding/index1.html) より引用

### デフォルト文字コードの変更方法

JVM オプションで "-Dfile.encoding=UTF-8"を指定する。 


------
# 3.付録

------
## [Try] サロゲートペアは2文字

サロゲートペアで表現しなくてはいけない文字（マイナーの文字）は、2文字と判定される。

```js:test.js
"a".length; //1
"あ".length; //1
"𩸽".length; //2
"𩸽".substr(1); //文字化け
```

Javaでも同様。
サロゲートペア対応用のメソッドもあり。


------
## 波ダッシュ問題（作成中）

「～」という文字をShift_JISに変換すると、化けてしまう。

http://qiita.com/kasei-san/items/3ce2249f0a1c1af1cbd2

Shift_JISのWindows版「Windows-31J」を指定すれば、解決。


------
## propertisファイル
かつてはISO-8859-1にしか対応していなかった。
ASCII以外はユニコードエスケープで記述。

Java6以降、UTF-8にも対応している。

http://blog.k11i.biz/2014/09/java-utf-8.html
http://d.hatena.ne.jp/shin/20090707/p4

------
## その他、気にすべき文字コード

### ファイル名
OSに依存。
ファイル名はASCIIのみが望ましい。

### URL
Tomcat8からはデフォルト文字コードがUTF-8。

### ダウンロードファイルの文字コード
HTTPヘッダの`Content-Disposition`




------
## 文字コードの名前

IANA(Internet Assigned Numbers Authority: インターネットに関連する番号を管理する組織) で定義

https://www.iana.org/assignments/character-sets/character-sets.xhtml


------
# おわり