List of Normalization - text.Baldanders.info
tag:text.Baldanders.info,2020-07-30:/tags
2020-07-30T22:49:26+09:00
帰ってきた「しっぽのさきっちょ」
https://text.baldanders.info/images/avatar.jpg
https://text.baldanders.info/images/avatar.jpg
GNKF 最初のリリース
tag:text.Baldanders.info,2020-07-30:/release/2020/07/first-release-gnkf/
2020-07-30T13:49:26+00:00
2021-08-12T21:22:05+00:00
元のコードの70%くらいは捨てました(笑)
Spiegel
https://baldanders.info/profile/
<p>貴方は3年前に書いた自分のコードにウンザリしたことはありませんか。
私はあります。
それもしょっちゅう(笑) まぁ,3年前と今とで同じコードを書いてたら,それはそれで進歩がないっちう話だけど。</p>
<p><ruby><rb>閑話休題</rb><rp> (</rp><rt>それはさておき</rt><rp>) </rp></ruby>。</p>
<p>3年前に最初のリリースを公開した「<a href="https://text.baldanders.info/remark/2017/12/like-nkf/" title="「nkf っぽいなにか」を作った">nkf っぽいなにか</a>」はあまりにコードの出来が悪すぎてメンテする気も起こらなくなったので,<a href="https://github.com/spiegel-im-spiegel/text" title="spiegel-im-spiegel/text: Encoding/Decoding Text Package by Golang">リポジトリを放棄</a>して新たに作り直すことにした(☆を付けてくださってる方,ごめんなさい)。</p>
<ul>
<li><a href="https://github.com/spiegel-im-spiegel/gnkf">spiegel-im-spiegel/gnkf: Network Kanji Filter by Golang</a></li>
</ul>
<p>元のコードの70%くらいは捨てました(笑)</p>
<p>詳しい使い方は以下を参考にどうぞ。</p>
<ul>
<li><a href="https://text.baldanders.info/release/gnkf/">GNKF: Network Kanji Filter by Golang</a></li>
</ul>
<p>これでようやく「<a href="https://text.baldanders.info/golang/kana-conversion/">かなカナ変換</a>」や「<a href="https://text.baldanders.info/golang/unicode-kangxi-radical/" title="こんな埼「玉」修正してやるぅ">康熙部首の正規化</a>」を組み込めた。</p>
<h2>参考図書</h2>
<div class="hreview">
<div class="photo"><a href="https://www.amazon.co.jp/dp/B099928SJD?tag=baldandersinf-22&linkCode=ogi&th=1&psc=1"><img src="https://m.media-amazon.com/images/I/416Stewy0NS._SL160_.jpg" width="123" alt="photo"></a></div>
<dl>
<dt class="item"><a class="fn url" href="https://www.amazon.co.jp/dp/B099928SJD?tag=baldandersinf-22&linkCode=ogi&th=1&psc=1">プログラミング言語Go</a></dt>
<dd>アラン・ドノバン (著), ブライアン・カーニハン (著), 柴田芳樹 (著)</dd>
<dd>丸善出版 2016-06-20 (Release 2021-07-13)</dd>
<dd>Kindle版</dd>
<dd>B099928SJD (ASIN)</dd>
<dd>評価<abbr class="rating fa-sm" title="5"> <i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i></abbr></dd>
</dl>
<p class="description">Kindle 版出た! 一部内容が古びてしまったが,この本は Go 言語の教科書と言ってもいいだろう。感想は<a href="https://text.baldanders.info/remark/2016/07/go-programming-language/" >こちら</a>。</p>
<p class="powered-by">reviewed by <a href='#maker' class='reviewer'>Spiegel</a> on <abbr class="dtreviewed" title="2021-05-22">2021-05-22</abbr> (powered by <a href="https://affiliate.amazon.co.jp/assoc_credentials/home">PA-APIv5</a>)</p>
</div> <!-- プログラミング言語Go -->
GNKF: Network Kanji Filter by Golang
tag:text.Baldanders.info,2020-07-30:/release/gnkf/
2020-07-30T13:41:38+00:00
2022-04-03T07:00:58+00:00
昔からある nkf コマンドの劣化コピー版と思っていただければ概ね間違いない(笑)
Spiegel
https://baldanders.info/profile/
<ul>
<li><a href="https://github.com/goark/gnkf">goark/gnkf: Network Kanji Filter by Golang</a></li>
</ul>
<p><a href="https://github.com/goark/gnkf" title="goark/gnkf: Network Kanji Filter by Golang">gnkf</a> は,コマンドラインで動作する,特に日本語テキストを操作するためのフィルタプログラムである。
昔からある nkf コマンドの劣化コピー版と思っていただければ概ね間違いない(笑)</p>
<p><a href="https://github.com/goark/gnkf/actions"><img src="https://github.com/goark/gnkf/workflows/vulns/badge.svg" alt="check vulns"></a>
<a href="https://github.com/goark/gnkf/actions"><img src="https://github.com/goark/gnkf/workflows/lint/badge.svg" alt="lint status"></a>
<a href="https://raw.githubusercontent.com/goark/gnkf/master/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202-blue.svg" alt="GitHub license"></a>
<a href="https://github.com/goark/gnkf/releases/latest"><img src="https://img.shields.io/github/release/goark/gnkf.svg" alt="GitHub release"></a></p>
<p><a href="https://github.com/goark/gnkf" title="goark/gnkf: Network Kanji Filter by Golang">gnkf</a> には以下の機能を組み込んでいる。</p>
<ul>
<li><code>guess</code>: <a href="#guess">文字エンコーディングの推測</a></li>
<li><code>enc</code>: <a href="#enc">文字エンコーディング変換</a></li>
<li><code>newline</code>: <a href="#newline">改行コードの一括変換</a></li>
<li><code>norm</code>: <a href="#norm">Unicode 正規化</a></li>
<li><code>width</code>: <a href="#width">全角・半角変換</a></li>
<li><code>kana</code>: <a href="#kana">かなカナ変換</a></li>
<li><code>base64</code>; <a href="#base64">BASE64 符号化</a></li>
<li><code>bcrypt</code>; <a href="#bcrypt">BCrypt 符号化と検証</a></li>
<li><code>hash</code>; <a href="#hash">Hash 符号化と検証</a></li>
<li><code>remove-bom</code> : <a href="#remove-bom">BOM の除去</a></li>
<li><code>dump</code>: <a href="#dump">16進ダンプ</a></li>
</ul>
<p>以降からもう少し詳しく紹介する。</p>
<h2 id="guess">文字エンコーディングの推測</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf guess -h
</span></span><span class="line"><span class="cl">Guess character encoding of the text
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Usage:
</span></span><span class="line"><span class="cl"> gnkf guess [flags]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Aliases:
</span></span><span class="line"><span class="cl"> guess, g
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Flags:
</span></span><span class="line"><span class="cl"> --all print all guesses
</span></span><span class="line"><span class="cl"> -f, --file string path of input text file
</span></span><span class="line"><span class="cl"> -h, --help help for guess
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Global Flags:
</span></span><span class="line"><span class="cl"> --debug for debug
</span></span></code></pre></div><p>たとえば以下のように使う。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo こんにちは,世界 | gnkf g
</span></span><span class="line"><span class="cl">UTF-8
</span></span></code></pre></div><p>元祖 nkf コマンドは改行コードの推測も行うが, <a href="https://github.com/goark/gnkf" title="goark/gnkf: Network Kanji Filter by Golang">gnkf</a> では文字エンコーディングの推測のみ行う。
テキスト全文を一旦メモリ内に読み込むので,巨大ファイルを扱う場合はご注意を。</p>
<p>文字エンコーディングの推測には <a href="https://github.com/saintfish/chardet" title="saintfish/chardet: Charset detector library for golang derived from ICU">saintfish/chardet</a> パッケージを使っているが,正直に言ってあまり精度がよくない(ただの plain text なら <a href="https://pkg.go.dev/golang.org/x/net/html/charset" title="charset package · pkg.go.dev">golang.org/x/net/html/charset</a> よりはイケてる感じだが)。</p>
<p>そこで <code>--all</code> オプションを使って複数の候補を表示できるようにしている。
こんな感じで蓋然性が高い順に表示している。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo こんにちは,世界 | gnkf g --all
</span></span><span class="line"><span class="cl">UTF-8
</span></span><span class="line"><span class="cl">windows-1255
</span></span><span class="line"><span class="cl">windows-1253
</span></span><span class="line"><span class="cl">Big5
</span></span><span class="line"><span class="cl">GB-18030
</span></span><span class="line"><span class="cl">Shift_JIS
</span></span></code></pre></div><h2 id="enc">文字エンコーディング変換</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf enc -h
</span></span><span class="line"><span class="cl">Convert character encoding of the text.
</span></span><span class="line"><span class="cl"> Using MIME and IANA name as the character encoding name.
</span></span><span class="line"><span class="cl"> Refer: http://www.iana.org/assignments/character-sets/character-sets.xhtml
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Usage:
</span></span><span class="line"><span class="cl"> gnkf enc [flags]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Aliases:
</span></span><span class="line"><span class="cl"> enc, encoding, e
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Flags:
</span></span><span class="line"><span class="cl"> -d, --dst-encoding string character encoding name of output text (default "utf-8")
</span></span><span class="line"><span class="cl"> -f, --file string path of input text file
</span></span><span class="line"><span class="cl"> -g, --guess guess character encoding of source text
</span></span><span class="line"><span class="cl"> -h, --help help for enc
</span></span><span class="line"><span class="cl"> -o, --output string path of output file
</span></span><span class="line"><span class="cl"> -b, --remove-bom remove BOM character in source text (UTF-8 only)
</span></span><span class="line"><span class="cl"> -s, --src-encoding string character encoding name of source text (default "utf-8")
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Global Flags:
</span></span><span class="line"><span class="cl"> --debug for debug
</span></span></code></pre></div><p><code>-s</code> および <code>-d</code> オプションで文字エンコーディング名を指定する際は <a href="https://www.iana.org/assignments/character-sets/character-sets.xhtml" title="Character Sets">IANA の登録名称</a>を使う(大文字・小文字は区別しない)。
たとえば,こんな感じ。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo こんにちは,世界 | gnkf e -d shift_jis
</span></span></code></pre></div><p>ただし <a href="https://pkg.go.dev/mod/golang.org/x/text" title="golang.org/x/text module · pkg.go.dev">golang.org/x/text</a> 以下のサブパッケージで変換器が用意されていない文字エンコーディングについては変換できないのであしからず。</p>
<p>日本語であれば <a href="https://pkg.go.dev/golang.org/x/text@v0.3.3/encoding/japanese" title="japanese package · pkg.go.dev"><code>japanese</code></a><code>.EUCJP</code>, <a href="https://pkg.go.dev/golang.org/x/text@v0.3.3/encoding/japanese" title="japanese package · pkg.go.dev"><code>japanese</code></a><code>.ISO2022JP</code>, <a href="https://pkg.go.dev/golang.org/x/text@v0.3.3/encoding/japanese" title="japanese package · pkg.go.dev"><code>japanese</code></a><code>.ShiftJIS</code> 等が用意されているので,それぞれ <code>"euc-jp"</code>, <code>"iso-2022-jp"</code>, <code>"shift_jis"</code> と指定すれば,いい感じに変換してくれる。</p>
<p><code>-g</code> オプションを付けると <code>gnkf guess</code> コマンドと同じロジックで変換元テキストの文字エンコーディングを推測する。
が,前節で述べたように,よく間違うので過信は禁物である。</p>
<h2 id="newline">改行コードの一括変換</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf newline -h
</span></span><span class="line"><span class="cl">Convert newline form in the text.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Usage:
</span></span><span class="line"><span class="cl"> gnkf newline [flags]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Aliases:
</span></span><span class="line"><span class="cl"> newline, nwln, nl
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Flags:
</span></span><span class="line"><span class="cl"> -f, --file string path of input text file
</span></span><span class="line"><span class="cl"> -h, --help help for newline
</span></span><span class="line"><span class="cl"> -n, --newline-form string newline form: [lf|cr|crlf] (default "lf")
</span></span><span class="line"><span class="cl"> -o, --output string path of output file
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Global Flags:
</span></span><span class="line"><span class="cl"> --debug for debug
</span></span></code></pre></div><p>こんな感じで使う。</p>
<pre tabindex="0"><code>$ echo こんにちは,世界 | gnkf nl -n crlf
</code></pre><p>テキスト全文を一旦メモリ内に読み込んで一括変換するため,巨大ファイルを扱う場合はご注意を。
一応,複数の改行コードが混在している場合でも上手く処理してくれる(筈)</p>
<h2 id="norm">Unicode 正規化</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf norm -h
</span></span><span class="line"><span class="cl">Unicode normalization of the text (UTF-8 encoding only).
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Usage:
</span></span><span class="line"><span class="cl"> gnkf norm [flags]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Aliases:
</span></span><span class="line"><span class="cl"> norm, normalize, nrm, nm
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Flags:
</span></span><span class="line"><span class="cl"> -f, --file string path of input text file
</span></span><span class="line"><span class="cl"> -h, --help help for norm
</span></span><span class="line"><span class="cl"> -k, --kangxi-radicals normalize kangxi radicals only (with nfkc or nfkd form)
</span></span><span class="line"><span class="cl"> -n, --norm-form string Unicode normalization form: [nfc|nfd|nfkc|nfkd] (default "nfc")
</span></span><span class="line"><span class="cl"> -o, --output string path of output file
</span></span><span class="line"><span class="cl"> -b, --remove-bom remove BOM character
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Global Flags:
</span></span><span class="line"><span class="cl"> --debug for debug
</span></span></code></pre></div><p>こんな感じで使う。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo ペンギン | gnkf nm -n nfkc
</span></span><span class="line"><span class="cl">ペンギン
</span></span></code></pre></div><p><a href="https://text.baldanders.info/golang/unicode-normalization/" title="Go 言語と Unicode 正規化">Unicode 正規化は色々と副作用</a>があるのでご注意を。
全角・半角変換については<a href="#width">後述</a>の <code>gnkf width</code> コマンドを使うのがオススメである。</p>
<h3>康熙部首の正規化</h3>
<p>NFKC または NFKD 正規化を行う際に <code>-k</code> オプションを付けることで康熙部首の正規化のみを行うようにした。</p>
<p>たとえば「㈱埼⽟」の「⽟(U+2f5f)」のみを正規化したい場合は</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo ㈱埼⽟ | gnkf nm -n nfkc -k
</span></span></code></pre></div><p>などとすればよい。</p>
<h2 id="width">全角・半角変換</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf width -h
</span></span><span class="line"><span class="cl">Convert character width in the text (UTF-8 encoding only).
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Usage:
</span></span><span class="line"><span class="cl"> gnkf width [flags]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Aliases:
</span></span><span class="line"><span class="cl"> width, wdth, w
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Flags:
</span></span><span class="line"><span class="cl"> -c, --conversion-form string conversion form: [fold|narrow|widen] (default "fold")
</span></span><span class="line"><span class="cl"> -f, --file string path of input text file
</span></span><span class="line"><span class="cl"> -h, --help help for width
</span></span><span class="line"><span class="cl"> -o, --output string path of output file
</span></span><span class="line"><span class="cl"> -b, --remove-bom remove BOM character
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Global Flags:
</span></span><span class="line"><span class="cl"> --debug for debug
</span></span></code></pre></div><p>こんな感じで使う。</p>
<pre tabindex="0"><code>$ echo ペンギン | gnkf w -c narrow
ペンギン
</code></pre><p><code>"fold"</code> フォームを使うと,英数字は半角にカナは全角に,といい感じに変換してくれる。</p>
<p>変換には <a href="https://pkg.go.dev/golang.org/x/text/width" title="width package · pkg.go.dev">golang.org/x/text/width</a> パッケージを使っているが,濁点・半濁点を含む文字や全角と半角で一対一に対応していない文字は上手く扱えないため,例外部分については内部で強制的に置換している。</p>
<p>また,このためにテキスト全文を一旦メモリ内に読み込んでいる。
巨大ファイルを扱う場合はご注意を。</p>
<h2 id="kana">かなカナ変換</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf kana -h
</span></span><span class="line"><span class="cl">Convert kana characters in the text.
</span></span><span class="line"><span class="cl"> UTF-8 encoding only.
</span></span><span class="line"><span class="cl"> "hiragana" and "katakana" forms are valid only for full-width kana character.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Usage:
</span></span><span class="line"><span class="cl"> gnkf kana [flags]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Aliases:
</span></span><span class="line"><span class="cl"> kana, k
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Flags:
</span></span><span class="line"><span class="cl"> -c, --conversion-form string conversion form: [hiragana|katakana|chokuon] (default "katakana")
</span></span><span class="line"><span class="cl"> -f, --file string path of input text file
</span></span><span class="line"><span class="cl"> --fold convert character width by fold form
</span></span><span class="line"><span class="cl"> -h, --help help for kana
</span></span><span class="line"><span class="cl"> -o, --output string path of output file
</span></span><span class="line"><span class="cl"> -b, --remove-bom remove BOM character
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Global Flags:
</span></span><span class="line"><span class="cl"> --debug for debug
</span></span></code></pre></div><p>こんな感じで使う。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo こんにちは | gnkf k -c katakana
</span></span><span class="line"><span class="cl">コンニチハ
</span></span></code></pre></div><p>かなカナ変換は全角文字のみが対象だが <code>--fold</code> オプションを付けることで全角・半角変換(<code>"fold"</code> フォーム)も同時に行うことができる。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo 123 コンニチハ | gnkf k -c hiragana --fold
</span></span><span class="line"><span class="cl">123 こんにちは
</span></span></code></pre></div><p>テキスト全文を一旦メモリ内に読み込むので,巨大ファイルを扱う場合はご注意を。</p>
<h3>直音への変換</h3>
<p><code>"chokuon"</code> フォームを指定して<ruby><rb>拗音</rb><rp> (</rp><rt>ようおん</rt><rp>) </rp></ruby>(小さい <code>'ゃ'</code>, <code>'ゅ'</code>, <code>'ょ'</code> など)や促音(小さい <code>'っ'</code>)を直音(<code>'や'</code>, <code>'ゆ'</code>, <code>'よ'</code>, <code>'つ'</code>)に変換する。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo ニッポン | gnkf k -c chokuon
</span></span><span class="line"><span class="cl">ニツポン
</span></span></code></pre></div><p>半角カナも変換可能。</p>
<h2 id="base64">BASE64 符号化</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf base64 -h
</span></span><span class="line"><span class="cl">Encode/Decode BASE64.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Usage:
</span></span><span class="line"><span class="cl"> gnkf base64 [flags]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Aliases:
</span></span><span class="line"><span class="cl"> base64, b64
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Flags:
</span></span><span class="line"><span class="cl"> -d, --decode decode BASE64 string
</span></span><span class="line"><span class="cl"> -f, --file string path of input text file
</span></span><span class="line"><span class="cl"> -u, --for-url encoding/decoding defined in RFC 4648
</span></span><span class="line"><span class="cl"> -h, --help help for base64
</span></span><span class="line"><span class="cl"> -p, --no-padding no padding
</span></span><span class="line"><span class="cl"> -o, --output string path of output file
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Global Flags:
</span></span><span class="line"><span class="cl"> --debug for debug
</span></span></code></pre></div><p>BASE64 符号化および復号機能。
Linux 系の <code>base64</code> コマンドや <code>openssl base64</code> コマンドの代わりに使える。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo Hello World | gnkf b64
</span></span><span class="line"><span class="cl">SGVsbG8gV29ybGQK
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ echo SGVsbG8gV29ybGQK | gnkf b64 -d
</span></span><span class="line"><span class="cl">Hello World
</span></span></code></pre></div><p>これで <code>base64</code> や <code>openssl</code> コマンドがない環境でも大丈夫。</p>
<h2 id="bcrypt">BCrypt 符号化と検証</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf bcrypt -h
</span></span><span class="line"><span class="cl">Hash and compare by BCrypt.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Usage:
</span></span><span class="line"><span class="cl"> gnkf bcrypt [flags] string [string...]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Aliases:
</span></span><span class="line"><span class="cl"> bcrypt, bc
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Flags:
</span></span><span class="line"><span class="cl"> --compare string compare to BCrypt hashed string
</span></span><span class="line"><span class="cl"> -c, --cost int BCrypt cost (4-31) (default 10)
</span></span><span class="line"><span class="cl"> -h, --help help for bcrypt
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Global Flags:
</span></span><span class="line"><span class="cl"> --debug for debug
</span></span></code></pre></div><p>パスワードの符号化アルゴリズムとして有名な BCrypt を使った文字列の符号化と符号化された文字列の検証ができる。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf bcrypt password
</span></span><span class="line"><span class="cl">$2a$10$ES0KxMf9p.t0FEMp8WB6we8X43rMzfXb9r5WvFeUSk8Q2z3wdjrCS
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ gnkf bc --compare '$2a$10$ES0KxMf9p.t0FEMp8WB6we8X43rMzfXb9r5WvFeUSk8Q2z3wdjrCS' password
</span></span><span class="line"><span class="cl">compare BCrypt hashed string '$2a$10$ES0KxMf9p.t0FEMp8WB6we8X43rMzfXb9r5WvFeUSk8Q2z3wdjrCS' to...
</span></span><span class="line"><span class="cl">password : match!
</span></span></code></pre></div><h2 id="hash">Hash 符号化と検証</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf hash -h
</span></span><span class="line"><span class="cl">Print or check hash value.
</span></span><span class="line"><span class="cl"> Support algorithm:
</span></span><span class="line"><span class="cl"> MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Usage:
</span></span><span class="line"><span class="cl"> gnkf hash [flags] [file]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Aliases:
</span></span><span class="line"><span class="cl"> hash, h
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Flags:
</span></span><span class="line"><span class="cl"> -a, --algorithm string hash algorithm (default "SHA-256")
</span></span><span class="line"><span class="cl"> -c, --check don't fail or report status for missing files
</span></span><span class="line"><span class="cl"> -h, --help help for hash
</span></span><span class="line"><span class="cl"> --ignore-missing don't fail or report status for missing files (with check option)
</span></span><span class="line"><span class="cl"> --quiet don't print OK for each successfully verified file (with check option)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Global Flags:
</span></span><span class="line"><span class="cl"> --debug for debug
</span></span></code></pre></div><p>ハッシュ関数を使ったデータの要約と検証。
Linux 系の <code>sha256sum</code> コマンド等の代わりに使える。</p>
<p>たとえば SHA-256 アルゴリズムでハッシュ値を取得するには</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo Hello World | gnkf h
</span></span><span class="line"><span class="cl">d2a84f4b8b650937ec8f73cd8be2c74add5a911ba64df27458ed8229da804a26 -
</span></span></code></pre></div><p>という感じ。
SHA-256 以外で,たとえば MD5 アルゴリズムを使いたい場合はアルゴリズムを指定して</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo Hello World | gnkf h -a md5
</span></span><span class="line"><span class="cl">e59ff97941044f85df5297e1c302d260 -
</span></span></code></pre></div><p>などとする。
ファイルを指定すると</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf h gnkf_0.5.0_Linux_64bit.tar.gz
</span></span><span class="line"><span class="cl">c64ef70a7ed23261b9ec9000e075ee7c6c441b604e00609b9d10078b123b7cb5 gnkf_0.5.0_Linux_64bit.tar.gz
</span></span></code></pre></div><p>といった感じに出力する。</p>
<p>また</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ cat gnkf_0.5.0_checksums.txt
</span></span><span class="line"><span class="cl">01904595df1a438caac6bdecdfa7c9befb4aa43cdb246cbdfba7b7e3bb7153db gnkf_0.5.0_Linux_ARMv6.tar.gz
</span></span><span class="line"><span class="cl">02e695cd16cc591eb8094c999ee326a6a15817f857596c6a644cecbb416c3d74 gnkf_0.5.0_Windows_64bit.zip
</span></span><span class="line"><span class="cl">322734ceb9e927503f620d9ed67fe1b1b0f8df3f09fca15b0d94786890ec07b6 gnkf_0.5.0_FreeBSD_64bit.tar.gz
</span></span><span class="line"><span class="cl">33d1d7ee4a6417e56ea76cf874b7175b6e5c47ba64216ba8f2f5f347a3bb635d gnkf_0.5.0_FreeBSD_ARM64.tar.gz
</span></span><span class="line"><span class="cl">7df75e6d4b458017f4f05cd4ba7007a3852c9f95607c48d2848b383ff463d79f gnkf_0.5.0_Windows_ARMv6.zip
</span></span><span class="line"><span class="cl">95528de1b027ac292faacca8d31ae13c7678d2dd15fda3f72744eccaa414dcd4 gnkf_0.5.0_macOS_ARM64.tar.gz
</span></span><span class="line"><span class="cl">9e32933a3f96bd31a5df80712589beeb37df7d5caa0d0d1de82a30660a816fd6 gnkf_0.5.0_FreeBSD_ARMv6.tar.gz
</span></span><span class="line"><span class="cl">b46912edf2de327ae2ecbc86d8d27f41a2be977da4adf4e3edbc37ae88d23cd3 gnkf_0.5.0_macOS_64bit.tar.gz
</span></span><span class="line"><span class="cl">c64ef70a7ed23261b9ec9000e075ee7c6c441b604e00609b9d10078b123b7cb5 gnkf_0.5.0_Linux_64bit.tar.gz
</span></span><span class="line"><span class="cl">dbaf68c77197780389b714a7fbbbe553874e9f733c5126c31737767d107e0835 gnkf_0.5.0_Linux_ARM64.tar.gz
</span></span></code></pre></div><p>などとハッシュ値の一覧が用意されている場合は <code>-c</code> オプションを使って</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf h --ignore-missing -c gnkf_0.5.0_checksums.txt
</span></span><span class="line"><span class="cl">gnkf_0.5.0_Linux_64bit.tar.gz: OK
</span></span><span class="line"><span class="cl">WARNING in 9 items: no such file or directory
</span></span></code></pre></div><p>とすればハッシュ値が正しいかどうか検証することができる。</p>
<h2 id="remove-bom">BOM の除去</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf remove-bom -h
</span></span><span class="line"><span class="cl">Remove BOM character in UTF-8 string.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Usage:
</span></span><span class="line"><span class="cl"> gnkf remove-bom [flags]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Aliases:
</span></span><span class="line"><span class="cl"> remove-bom, rbom, rb
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Flags:
</span></span><span class="line"><span class="cl"> -f, --file string path of input text file
</span></span><span class="line"><span class="cl"> -h, --help help for remove-bom
</span></span><span class="line"><span class="cl"> -o, --output string path of output file
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Global Flags:
</span></span><span class="line"><span class="cl"> --debug for debug
</span></span></code></pre></div><p>UTF-8 テキストに BOM (Byte Order Mark; U+FEFF) が含まれている場合</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo Hello | gnkf dump
</span></span><span class="line"><span class="cl">0xef, 0xbb, 0xbf, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x0a
</span></span></code></pre></div><p>これを除去する。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo Hello | gnkf remove-bom | gnkf dump
</span></span><span class="line"><span class="cl">0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x0a
</span></span></code></pre></div><p>先頭だけでなく,文字列中の全ての BOM を除去する。
テキスト全文を一旦メモリ内に読み込むので,巨大ファイルを扱う場合はご注意を。</p>
<h2 id="dump">16進ダンプ</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ gnkf dump -h
</span></span><span class="line"><span class="cl">Hexadecimal view of octet data stream with C language array style.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Usage:
</span></span><span class="line"><span class="cl"> gnkf dump [flags]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Aliases:
</span></span><span class="line"><span class="cl"> dump, hexdump, d, hd
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Flags:
</span></span><span class="line"><span class="cl"> -f, --file string path of input text file
</span></span><span class="line"><span class="cl"> -h, --help help for dump
</span></span><span class="line"><span class="cl"> -u, --unicode print by Unicode code point (UTF-8 only)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Global Flags:
</span></span><span class="line"><span class="cl"> --debug for debug
</span></span></code></pre></div><p>デバッグ用に用意した機能(笑) 以下のようにオクテット単位でダンプ表示する。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo ㈱埼⽟ | gnkf d
</span></span><span class="line"><span class="cl">0xe3, 0x88, 0xb1, 0xe5, 0x9f, 0xbc, 0xe2, 0xbd, 0x9f, 0x0a
</span></span></code></pre></div><p>更に <code>-u</code> オプションを付けると Unicode 符号点に変換して表示してくれる。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo ㈱埼⽟ | gnkf d -u
</span></span><span class="line"><span class="cl">0x3231, 0x57fc, 0x2f5f, 0x000a
</span></span></code></pre></div><p>これを使って,たとえば</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ echo ㈱埼⽟ | gnkf nm -n nfkc -k | gnkf d -u
</span></span><span class="line"><span class="cl">0x3231, 0x57fc, 0x7389, 0x000a
</span></span></code></pre></div><p>てな感じにパイプで繋いで動作確認できる。
我ながらありがたい機能だ(笑)</p>
<h2>ブックマーク</h2>
<ul>
<li><a href="https://text.baldanders.info/golang/transform-character-encoding/">文字エンコーディング変換</a></li>
<li><a href="https://text.baldanders.info/golang/unicode-normalization/">Go 言語と Unicode 正規化</a></li>
<li><a href="https://text.baldanders.info/golang/detecting-character-encoding/">文字エンコーディングの検出</a></li>
<li><a href="https://text.baldanders.info/golang/character-width-for-unicode/">Go 言語による Unicode 半角/全角変換</a></li>
<li><a href="https://text.baldanders.info/golang/kana-conversion/">かなカナ変換</a></li>
<li><a href="https://text.baldanders.info/golang/unicode-kangxi-radical/">こんな埼「玉」修正してやるぅ</a></li>
</ul>
<h2>参考図書</h2>
<div class="hreview">
<div class="photo"><a href="https://www.amazon.co.jp/dp/B099928SJD?tag=baldandersinf-22&linkCode=ogi&th=1&psc=1"><img src="https://m.media-amazon.com/images/I/416Stewy0NS._SL160_.jpg" width="123" alt="photo"></a></div>
<dl>
<dt class="item"><a class="fn url" href="https://www.amazon.co.jp/dp/B099928SJD?tag=baldandersinf-22&linkCode=ogi&th=1&psc=1">プログラミング言語Go</a></dt>
<dd>アラン・ドノバン (著), ブライアン・カーニハン (著), 柴田芳樹 (著)</dd>
<dd>丸善出版 2016-06-20 (Release 2021-07-13)</dd>
<dd>Kindle版</dd>
<dd>B099928SJD (ASIN)</dd>
<dd>評価<abbr class="rating fa-sm" title="5"> <i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i></abbr></dd>
</dl>
<p class="description">Kindle 版出た! 一部内容が古びてしまったが,この本は Go 言語の教科書と言ってもいいだろう。感想は<a href="https://text.baldanders.info/remark/2016/07/go-programming-language/" >こちら</a>。</p>
<p class="powered-by">reviewed by <a href='#maker' class='reviewer'>Spiegel</a> on <abbr class="dtreviewed" title="2021-05-22">2021-05-22</abbr> (powered by <a href="https://affiliate.amazon.co.jp/assoc_credentials/home">PA-APIv5</a>)</p>
</div> <!-- プログラミング言語Go -->
こんな埼「玉」修正してやるぅ
tag:text.Baldanders.info,2020-07-14:/golang/unicode-kangxi-radical/
2020-07-14T04:12:59+00:00
2021-12-04T02:40:05+00:00
問題となっているのは「康熙部首(kangxi radicals)」と呼ばれる漢字の部首を集めた以下の Unicode 符号点領域である。
Spiegel
https://baldanders.info/profile/
<p>Twitter を眺めていたら</p>
<figure style='margin:0 auto;text-align:center;'>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">総務省のマイナンバーカード交付状況をデータ化していて、データの結合がうまくいかないなーと思ったら、なんと、同じ埼玉でも文字コードが違うという落とし穴が・・<br>3月8日では \u2f5f が使われていて、それ以降では\u7389…<a href="https://t.co/AOkV3iojaz">https://t.co/AOkV3iojaz</a> <a href="https://t.co/jU4P583Ad5">pic.twitter.com/jU4P583Ad5</a></p>— Hal Seki (@hal_sk) <a href="https://twitter.com/hal_sk/status/1281853581218336768?ref_src=twsrc%5Etfw">July 11, 2020</a></blockquote> </figure>
<p>という tweet を見かけた。
これは Adobe Acrobat Distiller の不具合なんだそうで,2019年9月には既に話題に登っているのだが,2020年7月の時点でも修正されていないようだ。</p>
<ul>
<li><a href="https://community.adobe.com/t5/acrobat/create-pdf-why-kanji-9ad8-%E9%AB%98-will-be-changed-to-2fbc-when-meiryo-ui/td-p/10625575">Create PDF, why KANJI 9AD8(高) will be changed to 2… - Adobe Support Community - 10625575</a></li>
</ul>
<p>Adobe Acrobat Distiller が見捨てられてるのか,それとも「日本語」が見捨てられているのか…</p>
<div class="box"><strong>【2021-0129 追記】</strong>
PostScript プリンタドライバ経由で PostScript データを吐き出すと似たようなことが起きるらしい。もしかしたら <a href="https://twitter.com/trueroad_jp/status/1354445342461235202">Distiller の不具合ってこれが原因?</a> かも。</div>
<p><ruby><rb>閑話休題</rb><rp> (</rp><rt>それはさておき</rt><rp>) </rp></ruby>。
問題となっているのは「康熙部首(kangxi radicals)」と呼ばれる漢字の部首を集めた以下の Unicode 符号点領域である。</p>
<figure style='margin:0 auto;text-align:center;'><a href="https://en.wikipedia.org/wiki/Kangxi_radical"><img src="./unicode-kangxi-radical.png" srcset="./unicode-kangxi-radical.png 696w" sizes="(min-width:600px) 500px, 80vw" alt="“Kangxi radical - Wikipedia” より抜粋" loading="lazy"></a><figcaption><div><a href="https://en.wikipedia.org/wiki/Kangxi_radical">“Kangxi radical - Wikipedia” より抜粋</a></div></figcaption>
</figure>
<p>要するに,これらの領域の文字を本来の符号点に変換してやればいいわけだ。
件の tweet のスレッドを見ると,幸いにも Unicode の NFKC 正規化で変換可能らしい。</p>
<p>試しに以下のコードを組んで</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="s">"fmt"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="s">"golang.org/x/text/unicode/norm"</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">r</span> <span class="o">:=</span> <span class="nb">rune</span><span class="p">(</span><span class="mh">0x2f00</span><span class="p">);</span> <span class="nx">r</span> <span class="o"><=</span> <span class="mh">0x2fd5</span><span class="p">;</span> <span class="nx">r</span><span class="o">++</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">rr</span> <span class="o">:=</span> <span class="p">[]</span><span class="nb">rune</span><span class="p">(</span><span class="nx">norm</span><span class="p">.</span><span class="nx">NFKC</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="nb">string</span><span class="p">([]</span><span class="kt">rune</span><span class="p">{</span><span class="nx">r</span><span class="p">})))</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">r</span> <span class="o">!=</span> <span class="nx">rr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"%#U -(NFKC)-> %#U\n"</span><span class="p">,</span> <span class="nx">r</span><span class="p">,</span> <span class="nx">rr</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>実行してみると</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ go run sample1.go
</span></span><span class="line"><span class="cl">U+2F00 '⼀' -(NFKC)-> U+4E00 '一'
</span></span><span class="line"><span class="cl">U+2F01 '⼁' -(NFKC)-> U+4E28 '丨'
</span></span><span class="line"><span class="cl">U+2F02 '⼂' -(NFKC)-> U+4E36 '丶'
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">U+2F5F '⽟' -(NFKC)-> U+7389 '玉'
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">U+2FD4 '⿔' -(NFKC)-> U+9F9C '龜'
</span></span><span class="line"><span class="cl">U+2FD5 '⿕' -(NFKC)-> U+9FA0 '龠'
</span></span></code></pre></div><p>てな出力(一部割愛)になって,ちゃんと変換されていることが分かる。</p>
<p>ただし <a href="https://text.baldanders.info/golang/unicode-normalization/" title="Go 言語と Unicode 正規化">Unicode 正規化は副作用がある</a>ので安直には使えない。
となると,前回の「<a href="https://text.baldanders.info/golang/kana-conversion/">かなカナ変換</a>」で紹介した方法が使えるかな。</p>
<p>変換後の符号点の値は散らばっていて且つ数も多く手作業でコードを書くのは不毛なので,まずは <a href="https://pkg.go.dev/unicode" title="unicode package · pkg.go.dev"><code>unicode</code></a><code>.SpecialCase</code> を生成するコードを書いてみよう(笑)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="s">"fmt"</span>
</span></span><span class="line"><span class="cl"> <span class="s">"strconv"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="s">"golang.org/x/text/unicode/norm"</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">"var KangxiRadicals = unicode.SpecialCase{"</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">kr</span> <span class="o">:=</span> <span class="nb">rune</span><span class="p">(</span><span class="mh">0x2f00</span><span class="p">);</span> <span class="nx">kr</span> <span class="o"><=</span> <span class="mh">0x2fd5</span><span class="p">;</span> <span class="nx">kr</span><span class="o">++</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">rr</span> <span class="o">:=</span> <span class="p">[]</span><span class="nb">rune</span><span class="p">(</span><span class="nx">norm</span><span class="p">.</span><span class="nx">NFKC</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="nb">string</span><span class="p">([]</span><span class="kt">rune</span><span class="p">{</span><span class="nx">kr</span><span class="p">})))</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="nx">kr</span> <span class="o">!=</span> <span class="nx">rr</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"\tunicode.CaseRange{%#[1]x, %#[1]x, [unicode.MaxCase]rune{%#[2]x - %#[1]x, 0, 0}}, // %#[1]U -> %#[2]U\n"</span><span class="p">,</span> <span class="nx">kr</span><span class="p">,</span> <span class="nx">rr</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">"}"</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>これを実行するとこんなコードが得られる(一部割愛)。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">$ go run sample1b.go
</span></span><span class="line"><span class="cl">var KangxiRadicals = unicode.SpecialCase{
</span></span><span class="line"><span class="cl"> unicode.CaseRange{0x2f00, 0x2f00, [unicode.MaxCase]rune{0x4e00 - 0x2f00, 0, 0}}, // U+2F00 '⼀' -> U+4E00 '一'
</span></span><span class="line"><span class="cl"> unicode.CaseRange{0x2f01, 0x2f01, [unicode.MaxCase]rune{0x4e28 - 0x2f01, 0, 0}}, // U+2F01 '⼁' -> U+4E28 '丨'
</span></span><span class="line"><span class="cl"> unicode.CaseRange{0x2f02, 0x2f02, [unicode.MaxCase]rune{0x4e36 - 0x2f02, 0, 0}}, // U+2F02 '⼂' -> U+4E36 '丶'
</span></span><span class="line"><span class="cl"> ...
</span></span><span class="line"><span class="cl"> unicode.CaseRange{0x2f5f, 0x2f5f, [unicode.MaxCase]rune{0x7389 - 0x2f5f, 0, 0}}, // U+2F5F '⽟' -> U+7389 '玉'
</span></span><span class="line"><span class="cl"> ...
</span></span><span class="line"><span class="cl"> unicode.CaseRange{0x2fd4, 0x2fd4, [unicode.MaxCase]rune{0x9f9c - 0x2fd4, 0, 0}}, // U+2FD4 '⿔' -> U+9F9C '龜'
</span></span><span class="line"><span class="cl"> unicode.CaseRange{0x2fd5, 0x2fd5, [unicode.MaxCase]rune{0x9fa0 - 0x2fd5, 0, 0}}, // U+2FD5 '⿕' -> U+9FA0 '龠'
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><p>あとはこれを組み込んで使えばいいだけ。
たとえばこんな感じに使える。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">unicodePrint</span><span class="p">(</span><span class="nx">s</span> <span class="kt">string</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">ss</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">r</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">s</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">ss</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">ss</span><span class="p">,</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">"{%#U}"</span><span class="p">,</span> <span class="nx">r</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="nx">ss</span><span class="p">,</span> <span class="s">" "</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">saitama</span> <span class="o">:=</span> <span class="s">"埼⽟"</span>
</span></span><span class="line"><span class="cl"> <span class="nf">unicodePrint</span><span class="p">(</span><span class="nx">saitama</span><span class="p">)</span>
</span></span><span class="line hl"><span class="cl"> <span class="nf">unicodePrint</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">ToUpperSpecial</span><span class="p">(</span><span class="nx">KangxiRadicals</span><span class="p">,</span> <span class="nx">saitama</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>これを実行すると</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">go run sample2.go
</span></span><span class="line"><span class="cl">{U+57FC '埼'} {U+2F5F '⽟'}
</span></span><span class="line"><span class="cl">{U+57FC '埼'} {U+7389 '玉'}
</span></span></code></pre></div><p>となる。
よーし,うむうむ,よーし。</p>
<h2>ブックマーク</h2>
<ul>
<li><a href="https://qiita.com/hal_sk/items/8a95e9daa17b500f3f27">[BOD供養寺] スクレイピングしてきたデータの文字コードがおかしかったので修正した - Qiita</a></li>
<li><a href="https://text.baldanders.info/golang/unicode-normalization/">Go 言語と Unicode 正規化</a></li>
</ul>
<h2>参考図書</h2>
<div class="hreview">
<div class="photo"><a href="https://www.amazon.co.jp/dp/B099928SJD?tag=baldandersinf-22&linkCode=ogi&th=1&psc=1"><img src="https://m.media-amazon.com/images/I/416Stewy0NS._SL160_.jpg" width="123" alt="photo"></a></div>
<dl>
<dt class="item"><a class="fn url" href="https://www.amazon.co.jp/dp/B099928SJD?tag=baldandersinf-22&linkCode=ogi&th=1&psc=1">プログラミング言語Go</a></dt>
<dd>アラン・ドノバン (著), ブライアン・カーニハン (著), 柴田芳樹 (著)</dd>
<dd>丸善出版 2016-06-20 (Release 2021-07-13)</dd>
<dd>Kindle版</dd>
<dd>B099928SJD (ASIN)</dd>
<dd>評価<abbr class="rating fa-sm" title="5"> <i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i></abbr></dd>
</dl>
<p class="description">Kindle 版出た! 一部内容が古びてしまったが,この本は Go 言語の教科書と言ってもいいだろう。感想は<a href="https://text.baldanders.info/remark/2016/07/go-programming-language/" >こちら</a>。</p>
<p class="powered-by">reviewed by <a href='#maker' class='reviewer'>Spiegel</a> on <abbr class="dtreviewed" title="2021-05-22">2021-05-22</abbr> (powered by <a href="https://affiliate.amazon.co.jp/assoc_credentials/home">PA-APIv5</a>)</p>
</div> <!-- プログラミング言語Go -->
「神」と「神」と Chrome の文字化け
tag:text.Baldanders.info,2017-12-10:/remark/2017/12/gods-in-chrome/
2017-12-10T03:35:07+00:00
2019-07-01T13:48:09+00:00
Google Chrome では「神」と「神」が同じ「神」に見える,という報告をいただきまして,確かにこちらの環境でも再現するので,ちょっと調べてみました。
Spiegel
https://baldanders.info/profile/
<p>「<a href="https://text.baldanders.info/golang/character-width-for-unicode/">Go 言語による Unicode 半角/全角変換</a>」のページを Google Chrome で見ると「神と神」が同じ「神」に見える,という報告をいただきまして,確かにこちらの環境でも再現するので,ちょっと調べてみました。</p>
<p>(追記: Chrome だけじゃなくて <a href="https://play.google.com/store/apps/details?id=org.mozilla.focus">Android 用の Firefox Focus</a> でも同じ現象を確認した。どうやら <a href="https://play.google.com/store/apps/details?id=org.mozilla.focus">Android 用の Firefox Focus</a> は Firefox とはレイアウトエンジンが異なり WebKit からの fork である WebView (Android に既定で入ってるやつ)を<a href="http://www.eweek.com/enterprise-apps/mozilla-brings-firefox-focus-to-android-to-improve-privacy" title="Firefox Focus Debuts on Android With Automatic Tracking Protection">使ってるらしい</a>。それって,もしかして全ての元凶は WebKit ってことなのか?)</p>
<p>(追記2: <a href="https://play.google.com/store/apps/details?id=org.mozilla.focus">Android 用の Firefox Focus</a> のレンダリングエンジンが <a href="https://support.mozilla.org/ja/kb/geckoview-firefox-focus" title="Firefox Focus の GeckoView | Firefox Focus ヘルプ">GechoView に変わった</a>が影響があるかどうか確認してない)</p>
<p>ちなみに「神と神」のうち前者の「神」は所謂 CJK 互換文字と呼ばれるもので Unicode コードポイントで「<code>U+FA19 '神'</code>」にマッピングされるものです。</p>
<p>まず,<a href="https://text.baldanders.info/" title="text.Baldanders.info">本サイト</a>の状態を説明しておくと,サイトの各ページの文字は Web フォントで表示されている。
ブラウザ側で Web フォントを無効にしていない(または経路途中でフィルタリングされない)限りは,こちらで指定したフォントで表示されるはずである。</p>
<ul>
<li><a href="https://text.baldanders.info/remark/2015/web-font-family/">Web フォントに関する覚え書き</a></li>
<li><a href="https://text.baldanders.info/remark/2016/10/japanese-serif-fonts-by-google-cdn/">Web フォントに関する覚え書き(明朝体編)</a></li>
</ul>
<p>本文を含む地の文章には「<a href="http://sawarabi-fonts.osdn.jp/" title="さわらびフォント">さわらび明朝</a>」を利用している。
<a href="https://fonts.google.com/earlyaccess">Google Fonts Early Access</a> で唯一ふだん使いできる明朝体の Web フォントだったのが選択した理由である<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。</p>
<p>ただし「<a href="http://sawarabi-fonts.osdn.jp/" title="さわらびフォント">さわらび明朝</a>」は,個人の方がボランティアで作成されているということもあり,全ての和文文字を網羅しているわけではなく,幾つか表示できない文字がある。
「<code>U+FA19 '神'</code>」もそのうちのひとつである。
どうもその辺に原因がありそうである。</p>
<p>そこで,テスト用に以下の HTML コードを用意し Chrome に表示させてみた。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">html</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">head</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">'stylesheet'</span> <span class="na">href</span><span class="o">=</span><span class="s">'http://fonts.googleapis.com/earlyaccess/sawarabimincho.css'</span> <span class="na">type</span><span class="o">=</span><span class="s">'text/css'</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">title</span><span class="p">></span>神と神<span class="p"></</span><span class="nt">title</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">style</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p">#</span><span class="nn">localonly</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">font-family</span><span class="p">:</span> <span class="kc">sans-serif</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">#</span><span class="nn">sawarabi</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">font-family</span><span class="p">:</span> <span class="s1">'Sawarabi Mincho'</span><span class="p">,</span> <span class="kc">sans-serif</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p"></</span><span class="nt">style</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"></</span><span class="nt">head</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">body</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">"localonly"</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">p</span><span class="p">></span> 「神」と「神」(後者が CJK 互換文字)<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">p</span><span class="p">></span>草「彅」剛(JIS 第一・第二水準にない漢字)<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">p</span><span class="p">></span> 「繋」ぐ(さわらび明朝にない文字)<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"></</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">"sawarabi"</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">p</span><span class="p">></span> 「神」と「神」(後者が CJK 互換文字)<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">p</span><span class="p">></span>草「彅」剛(JIS 第一・第二水準にない漢字)<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">p</span><span class="p">></span> 「繋」ぐ(さわらび明朝にない文字)<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"></</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"></</span><span class="nt">body</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"></</span><span class="nt">html</span><span class="p">></span>
</span></span></code></pre></div><p>これを表示させた結果は以下の通り。</p>
<figure style='margin:0 auto;text-align:center;'><a href="https://photo.baldanders.info/flickr/38947212091/"><img src="https://photo.baldanders.info/flickr/image/38947212091_m.png" srcset="https://photo.baldanders.info/flickr/image/38947212091_m.png 500w" sizes="(min-width:600px) 500px, 80vw" alt="Google Chrome (1)" loading="lazy"></a><figcaption><div><a href="https://photo.baldanders.info/flickr/38947212091/">Google Chrome (1)</a></div></figcaption>
</figure>
<p>最初の3行が Chrome デフォルトの Sans Serif で表示させたもの,次の3行が「<a href="http://sawarabi-fonts.osdn.jp/" title="さわらびフォント">さわらび明朝</a>」で表示させたものである。
ただし「<a href="http://sawarabi-fonts.osdn.jp/" title="さわらびフォント">さわらび明朝</a>」にない文字は Sans Serif で表示するよう設定している。</p>
<p>「神」と「彅」と「繋」は「<a href="http://sawarabi-fonts.osdn.jp/" title="さわらびフォント">さわらび明朝</a>」に収録されていない文字なのだが,何故か「神」だけが明朝体で「神」と表示されているのがお分かりだろうか<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。
ふむむむむ。</p>
<p>ここで思い出すのが Unicode 正規化による「神」の正規化である。</p>
<ul>
<li><a href="https://text.baldanders.info/golang/unicode-normalization/">Go 言語と Unicode 正規化</a></li>
</ul>
<p>この記事で説明している通り, CJK 互換文字である「<code>U+FA19 '神'</code>」は正規化によって「<code>U+795E '神'</code>」に変換されてしまうのである。</p>
<p>ならば今度は CJK 互換文字に絞って以下の HTML コードを組んでみる。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p"><</span><span class="nt">html</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">head</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">'stylesheet'</span> <span class="na">href</span><span class="o">=</span><span class="s">'http://fonts.googleapis.com/earlyaccess/sawarabimincho.css'</span> <span class="na">type</span><span class="o">=</span><span class="s">'text/css'</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">title</span><span class="p">></span>神と神<span class="p"></</span><span class="nt">title</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">style</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p">#</span><span class="nn">localonly</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">font-family</span><span class="p">:</span> <span class="kc">sans-serif</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">#</span><span class="nn">sawarabi</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">font-family</span><span class="p">:</span> <span class="s1">'Sawarabi Mincho'</span><span class="p">,</span> <span class="kc">sans-serif</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p"></</span><span class="nt">style</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"></</span><span class="nt">head</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">body</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">"localonly"</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">p</span><span class="p">></span> 「朗」と「朗」(後者が CJK 互換文字,正規化される)<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">p</span><span class="p">></span> 「欄」と「欄」(後者が CJK 互換文字,正規化される)<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">p</span><span class="p">></span> 「崎」と「﨑」(後者が CJK 互換文字,正規化されない)<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"></</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"><</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">"sawarabi"</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">p</span><span class="p">></span> 「朗」と「朗」(後者が CJK 互換文字,正規化される)<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">p</span><span class="p">></span> 「欄」と「欄」(後者が CJK 互換文字,正規化される)<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">p</span><span class="p">></span> 「崎」と「﨑」(後者が CJK 互換文字,正規化されない)<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"></</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"></</span><span class="nt">body</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p"></</span><span class="nt">html</span><span class="p">></span>
</span></span></code></pre></div><p>これを表示させた結果は以下の通り。</p>
<figure style='margin:0 auto;text-align:center;'><a href="https://photo.baldanders.info/flickr/38061039025/"><img src="https://photo.baldanders.info/flickr/image/38061039025_m.png" srcset="https://photo.baldanders.info/flickr/image/38061039025_m.png 500w" sizes="(min-width:600px) 500px, 80vw" alt="Google Chrome (2)" loading="lazy"></a><figcaption><div><a href="https://photo.baldanders.info/flickr/38061039025/">Google Chrome (2)</a></div></figcaption>
</figure>
<p>ビンゴ!</p>
<ul>
<li>「朗」と「朗」は「神」と「神」の関係と同じ。 CJK 互換文字「朗」は「<a href="http://sawarabi-fonts.osdn.jp/" title="さわらびフォント">さわらび明朝</a>」に収録されてなく, Unicode 正規化によって「朗」に変換される</li>
<li>「欄」は Unicode 正規化によって「欄」に変換されるが,どちらの文字も「<a href="http://sawarabi-fonts.osdn.jp/" title="さわらびフォント">さわらび明朝</a>」に収録されていないため, Chrome デフォルトのフォントで表示されている</li>
<li>「﨑」は CJK 互換文字で「<a href="http://sawarabi-fonts.osdn.jp/" title="さわらびフォント">さわらび明朝</a>」に収録されていないが Unicode 正規化の対象ではない(「崎」は似た字形の文字を並べてみただけ,テヘペロ)ため,そのまま Chrome デフォルトのフォントで表示されている</li>
</ul>
<p>つまり</p>
<ol>
<li>フォントに対象の文字が収録されていない</li>
<li>Unicode 正規化によって別の文字に変換可能</li>
<li>変換された文字がフォントに収録されている</li>
</ol>
<p>という条件が揃った時に「文字化け」が発生する,ということのようだ<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>。</p>
<p>いや,これは酷いだろう。
同じ文字化けするなら「豆腐 □」や「ゲタ 〓」のほうがまだマシである。
多分 Chrome の中の人は CJK 互換文字や異体字について(歴史背景などを)よく理解しないまま「正規化できる文字で代替えすればいいぢゃん♥」と軽い感じで実装してしまったのだろう。
「<a href="https://text.baldanders.info/golang/unicode-normalization/">Go 言語と Unicode 正規化</a>」では Apple の旧ファイルシステム HFS+ を散々に貶したが, <del>Google もタイガイである</del>。(Chrome だけじゃなかった)</p>
<p>ちなみに,この現象は Firefox (またはその系列のブラウザ)では発生しない。
私のメインブラウザは Firefox (Quantum マンセー!) なので,指摘されるまで全く気づかなかった。
ゴメンペコン<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>。</p>
<p>とはいえ,これは相当にレアケースなので滅多なことでは発現しないだろう。
<del>個人的には Noto Serif の和文フォントが Web フォントで登場するまでは「<a href="http://sawarabi-fonts.osdn.jp/" title="さわらびフォント">さわらび明朝</a>」で頑張るつもりである。
なので,特に対策しない予定。</del>
(ゴメン。<a href="https://text.baldanders.info/remark/2017/12/installing-noto-serif-jp-in-www_baldanders_info/" title="結局 Noto Serif JP を Web フォントとして導入した">日和った</a>)</p>
<p>ご不便をおかけすることもあるかもですが,悪しからずご了承の程を。</p>
<h2>ブックマーク</h2>
<ul>
<li><a href="https://qiita.com/umeume66/items/01291353fc43c17da992">「Noto Serif Japanese」を使ってみたので、自分用メモ。 - Qiita</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>最近発表された Noto Serif の和文フォントはまだ Web フォントとしては提供されていない。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:2">
<p>デフォルトの Sans Serif が使われていないことは Chrome のデベロッパー・ツールでも確認済み。 <a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:3">
<p>今回は「異体字セレクタ(Variation Selector)」については検証していない。そもそも異体字セレクタによる文字表示はアプリケーションとフォントの両方が対応していないと成り立たない。メジャー・ブラウザは異体字セレクタに対応しているそうだが,私は対応するフォントを持ってないし,対応できる自由な Web フォントが(CDN 等で)提供されているという話も聞かない。したがって今回はスルーの方向で。 <a href="#fnref:3" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:4">
<p>一応このサイトは Firefox (Quantum) と Chrome ならレイアウトが崩れないように考えているつもり。旧 Firefox や IE や Safari などレガシーなブラウザはスルーで。 Edge は対応したいところだが手元に環境を持ってないので確認&対応できない。 Win10 を導入する予定は,今のところはない。換装するなら Linux 系のデスクトップにするつもり。 <a href="#fnref:4" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div>
Go 言語と Unicode 正規化
tag:text.Baldanders.info,2016-01-30:/golang/unicode-normalization/
2016-01-29T19:05:52+00:00
2019-07-01T13:48:09+00:00
今回は少し目先を変えて「Unicode 正規化」のお話。
Spiegel
https://baldanders.info/profile/
<p>今回は少し目先を変えて「Unicode 正規化(normalization)」のお話。</p>
<h2>2羽の「ペンギン」</h2>
<p>まず「ペンギン」という文字列を思い浮かべてみる。
この文字列を Unicode のコードポイントで表すと以下のようになる。</p>
<ul>
<li>ペ:U+30DA</li>
<li>ン:U+30F3</li>
<li>ギ:U+30AE</li>
<li>ン:U+30F3</li>
</ul>
<p>ところでペンギンの「ペ」と「ギ」は半濁点および濁点を含む。
Unicode は「ペ」と「ギ」をそれぞれ2つの要素に分解できる。</p>
<ul>
<li>ペ:U+30D8 + U+309A</li>
<li>ン:U+30F3</li>
<li>ギ:U+30AD + U+3099</li>
<li>ン:U+30F3</li>
</ul>
<p>U+309A および U+3099 はそれぞれ半濁点と濁点を表す「結合文字(combining character)」である。
「ヘ」や「キ」のような「基底文字(base character)」に結合文字を1つ以上<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> 付加した文字を「合成列(composite sequence)」と呼ぶ。
これに対して「ペ:U+30DA」や「ギ:U+30AE」のような文字を「事前合成形(precomposed)」と呼ぶ。</p>
<p>つまり同じ文字を同じ文字集合<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> で表しているのにもかかわらず複数の符号化<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> が存在するわけだ。
これを「重複符号化」と言う。
文字集合に重複符号化があるというのは,はっきり言って「クソ仕様」である。</p>
<p>もちろんこれは Unicode を作った連中がヘボいのではなく(いや,ヘボいのかもしれないが),いわゆる「歴史的経緯」というやつである<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>。
だからこれはこういうものだと諦めるしかない。</p>
<p>しかし情報処理を行う上では,この2羽の「ペンギン」が等価(equivalance)であることを示す手立てを考えなければならない。</p>
<h2>正規等価</h2>
<p>2羽の「ペンギン」が等価であることを示す一番簡単な方法は,文字列を事前合成形あるいは合成列のどちらかに統一(=正規化)してしまえばいい。
これを「正規等価(canonical equivalance)」と呼ぶ。
このうち,事前合成形に正規化する方法を “NFC(Normalization Form Composition)”,合成列に正規化する方法を “NFD(Normalization Form Decomposition)” と呼ぶ。</p>
<p><a href="https://golang.org/" title="The Go Programming Language">Go 言語</a>では <code>golang.org/x/text/unicode/</code><a href="https://godoc.org/golang.org/x/text/unicode/norm" title="norm - GoDoc"><code>norm</code></a> パッケージで Unicode 文字列を正規化できる。
まぁ,コードで書いたほうがはやいか。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="s">"fmt"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="s">"golang.org/x/text/unicode/norm"</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">penguin</span> <span class="o">:=</span> <span class="s">"ペンギン"</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">penguin</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"penguin[%d] = %#U\n"</span><span class="p">,</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="nx">penguin2</span> <span class="o">:=</span><span class="nx">norm</span><span class="p">.</span><span class="nx">NFD</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="nx">penguin</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">penguin2</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"penguin2[%d] = %#U\n"</span><span class="p">,</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="nx">penguin3</span> <span class="o">:=</span> <span class="nx">norm</span><span class="p">.</span><span class="nx">NFC</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="nx">penguin2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">penguin3</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"penguin3[%d] = %#U\n"</span><span class="p">,</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>実行結果はこんな感じ。</p>
<pre tabindex="0"><code>penguin[0] = U+30DA 'ペ'
penguin[3] = U+30F3 'ン'
penguin[6] = U+30AE 'ギ'
penguin[9] = U+30F3 'ン'
penguin2[0] = U+30D8 'ヘ'
penguin2[3] = U+309A '゚'
penguin2[6] = U+30F3 'ン'
penguin2[9] = U+30AD 'キ'
penguin2[12] = U+3099 '゙'
penguin2[15] = U+30F3 'ン'
penguin3[0] = U+30DA 'ペ'
penguin3[3] = U+30F3 'ン'
penguin3[6] = U+30AE 'ギ'
penguin3[9] = U+30F3 'ン'
</code></pre><p>NFC と NFD が交換可能であることがわかると思う。</p>
<h2>3羽目の「ペンギン」と互換等価</h2>
<p>さてここで3羽目の「ペンギン」に登場してもらおう。</p>
<ul>
<li>ヘ:U+FF8D</li>
<li>゚:U+FF9F</li>
<li>ン:U+FF9D</li>
<li>キ:U+FF77</li>
<li>゙:U+FF9E</li>
<li>ン:U+FF9D</li>
</ul>
<p>これはいわゆる「半角カナ」である。
半角カナの半濁点 U+FF9F および濁点 U+FF9E は結合文字の半濁点 U+309A および濁点 U+3099 と同等とみなされているが「ペ」や「ギ」に相当する半角カナの事前合成形は存在しないため NFC で事前合成形に正規化しようとしても</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="s">"fmt"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="s">"golang.org/x/text/unicode/norm"</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">penguin</span> <span class="o">:=</span> <span class="s">"ペンギン"</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">penguin</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"penguin[%d] = %#U\n"</span><span class="p">,</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="nx">penguin2</span> <span class="o">:=</span> <span class="nx">norm</span><span class="p">.</span><span class="nx">NFC</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="nx">penguin</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">penguin2</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"penguin2[%d] = %#U\n"</span><span class="p">,</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><pre tabindex="0"><code>penguin[0] = U+FF8D 'ヘ'
penguin[3] = U+FF9F '゚'
penguin[6] = U+FF9D 'ン'
penguin[9] = U+FF77 'キ'
penguin[12] = U+FF9E '゙'
penguin[15] = U+FF9D 'ン'
penguin2[0] = U+FF8D 'ヘ'
penguin2[3] = U+FF9F '゚'
penguin2[6] = U+FF9D 'ン'
penguin2[9] = U+FF77 'キ'
penguin2[12] = U+FF9E '゙'
penguin2[15] = U+FF9D 'ン'
</code></pre><p>何も変わらないことが分かるだろう。
そもそも半角カナは「互換用文字(Compatibility Character)」として異なるコードポイントが割り当てられているため,このままでは3羽目の「ペンギン」が等価であることを示せない。</p>
<p>このような場合は「ペンギン」と互換性のある別の文字列に正規化できるとよい。
これを「互換等価(compatibility equivalance)」と呼ぶ。
具体的には,事前合成形に正規化する NFKC(Normalization Form Compatibility Composition)と合成列に正規化する NFKD(Normalization Form Compatibility Decomposition)の2つがある。</p>
<p>早速 <a href="https://godoc.org/golang.org/x/text/unicode/norm" title="norm - GoDoc"><code>norm</code></a> パッケージを使ってコードを書いてみる。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="s">"fmt"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="s">"golang.org/x/text/unicode/norm"</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">penguin</span> <span class="o">:=</span> <span class="s">"ペンギン"</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">penguin</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"penguin[%d] = %#U\n"</span><span class="p">,</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="nx">penguin2</span> <span class="o">:=</span> <span class="nx">norm</span><span class="p">.</span><span class="nx">NFKC</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="nx">penguin</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">penguin2</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"penguin2[%d] = %#U\n"</span><span class="p">,</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>これを実行すると</p>
<pre tabindex="0"><code>penguin[0] = U+FF8D 'ヘ'
penguin[3] = U+FF9F '゚'
penguin[6] = U+FF9D 'ン'
penguin[9] = U+FF77 'キ'
penguin[12] = U+FF9E '゙'
penguin[15] = U+FF9D 'ン'
penguin2[0] = U+30DA 'ペ'
penguin2[3] = U+30F3 'ン'
penguin2[6] = U+30AE 'ギ'
penguin2[9] = U+30F3 'ン'
</code></pre><p>となり, NFC で正規化した「ペンギン」と等価であることがわかる。</p>
<p>互換等価による正規化は応用範囲が広い。
たとえば「㈱」(U+3231)は「(株)」(U+0028 + U+682A + U+0029)に変換される。
文字列検索の前に互換等価による正規化を行っておくことで処理がやりやすくなるというのはあるかもしれない。
ただし, NFC と NFD は交換可能だが(ただしオリジナル文字列が事前合成形と合成列とで混在している場合は元に戻せないが), NFKC や NFKD で正規化した文字列を元に戻す方法はないので注意が必要である<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>。</p>
<h2>恐怖の CJK 互換文字</h2>
<p>正規等価については注意すべき点がある。
有名な「神」を例に挙げよう。
これを NFC / NFD で正規化する。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="s">"fmt"</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="s">"golang.org/x/text/unicode/norm"</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">god</span> <span class="o">:=</span> <span class="s">"神"</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">god</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"god[%d] = %#U\n"</span><span class="p">,</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="nx">god2</span> <span class="o">:=</span> <span class="nx">norm</span><span class="p">.</span><span class="nx">NFC</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="nx">god</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">god2</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"god2[%d] = %#U\n"</span><span class="p">,</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="nx">god3</span> <span class="o">:=</span> <span class="nx">norm</span><span class="p">.</span><span class="nx">NFC</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="nx">god</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">for</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">god3</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"god3[%d] = %#U\n"</span><span class="p">,</span> <span class="nx">pos</span><span class="p">,</span> <span class="nx">runeValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>これを実行すると</p>
<pre tabindex="0"><code>god[0] = U+FA19 '神'
god2[0] = U+795E '神'
god3[0] = U+795E '神'
</code></pre><p>となり,NFC でも NFD でも違う文字になってしまった。
ちなみに「神」から「神」へ正規化する方法はない。
困ったね。</p>
<p>実は「神」は「CJK 互換文字」と呼ばれるグループに属し,「神」とは異体字の関係にある。
故に「神」を「神」に正規化してしまったのである。</p>
<p>これは明らかに仕様ミスである。
「神」と「神」のような異体字の関係は本来なら正規等価ではなく互換等価であるべきだからだ。</p>
<p>…やっぱり Unicode はクソ仕様だ。</p>
<p>ただ,これが実際の場面で問題になることは少ないと思われる。
なぜなら,正規化を行うのは「2つの文字列が等価であるか?」を調べるための手段にすぎないからだ。
普通はね。</p>
<p>ところが,普通でないことをする馬鹿がいるのである。</p>
<h3>独自路線に走る Apple</h3>
<p>Apple の OS X (iOS も?)のファイルシステムである HFS+ はファイル名を NFD 相当に正規化するという恐ろしい仕様になっている<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>。
しかしそれでは先ほどの例のように異体字を別の文字に変えられてしまうため困ったことになってしまう。</p>
<p>そこで Apple は CJK 互換文字を含むいくつかの文字を正規化の対象から外すという蛮行に出た。
俗に “UTF-8-MAC<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>” などと呼ばれる独自路線に走ってしまったわけだ。
クソの上塗りである。</p>
<p>これにより様々な(特にマルチプラットフォームな)アプリケーションが多大なる迷惑を被ることになるが<sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup>,深くはツッコむまい。</p>
<h3>追記(2017-03-29)</h3>
<p>新しくリリースされた APFS (Apple File System) では上述のような似非正規化は行わないらしい。
よーし,うむうむ,よーし。</p>
<ul>
<li><a href="http://mjtsai.com/blog/2017/03/24/apfss-bag-of-bytes-filenames/">Michael Tsai - Blog - APFS’s “Bag of Bytes” Filenames</a></li>
<li><a href="http://naruse.hateblo.jp/entry/2017/03/28/181519">macOS上のAPFSはUnicode Normalizationを行うのか? - なるせにっき</a></li>
<li><a href="http://qiita.com/dankogai/items/6564a5a92288dd2a35d7">APFSで再燃したNFD問題 - Qiita</a></li>
</ul>
<h2>Unicode 正規化に関するまとめ</h2>
<p>以上, Unicode 正規化の4つの方式をまとめると以下のようになる。</p>
<figure style='margin:0 auto;text-align:center;'>
<table>
<tr>
<th></th>
<th>Composing</th>
<th>Decomposing</th>
</tr>
<tr>
<th style="text-align:right;">Canonical equivalence</th>
<td><code><a href="https://godoc.org/golang.org/x/text/unicode/norm">norm</a>.NFC</code></td>
<td><code><a href="https://godoc.org/golang.org/x/text/unicode/norm">norm</a>.NFD</code><br></td>
</tr>
<tr>
<th style="text-align:right;">Compatibility equivalence</th>
<td><code><a href="https://godoc.org/golang.org/x/text/unicode/norm">norm</a>.NFKC</code></td>
<td><code><a href="https://godoc.org/golang.org/x/text/unicode/norm">norm</a>.NFKD</code></td>
</tr>
</table>
<figcaption><div><a href="https://blog.golang.org/normalization">via “Text normalization in Go”</a></div></figcaption>
</figure>
<p>ちなみに <a href="https://godoc.org/golang.org/x/text/unicode/norm" title="norm - GoDoc"><code>norm</code></a> パッケージでは “UTF-8-MAC” なるローカル仕様には対応していないので,必要なら自作する必要がある。</p>
<p>Unicode 文字列の等価属性を調べる際には是非参考にどうぞ。</p>
<h2>ブックマーク</h2>
<ul>
<li><a href="http://internet.watch.impress.co.jp/www/column/ogata/sp24.htm">特別編24 JIS X 0213の改正は、文字コードにどんな未来をもたらすか(7) 番外編:改正JIS X 0213とUnicodeの等価属性/正規化について(上)</a></li>
<li><a href="http://internet.watch.impress.co.jp/www/column/ogata/sp25.htm">特別編24 JIS X 0213の改正は、文字コードにどんな未来をもたらすか(7) 番外編:改正JIS X 0213とUnicodeの等価属性/正規化について(下)</a></li>
<li><a href="http://nomenclator.la.coocan.jp/unicode/normalization.htm">Unicode正規化</a></li>
<li><a href="https://blog.golang.org/normalization">Text normalization in Go - The Go Blog</a></li>
<li><a href="http://qiita.com/masakielastic/items/01a4fb691c572dd71a19">Go で UTF-8 の文字列を扱う - Qiita</a></li>
<li><a href="http://tech.albert2005.co.jp/blog/2014/11/21/mco-normalize/">文字コード地獄秘話 第3話:後戻りの効かないUnicode正規化 - ALBERT Engineer Blog</a></li>
<li><a href="http://cpplover.blogspot.jp/2015/01/blog-post_14.html">本の虫: Linus Torvalds、HFS+に激怒</a></li>
<li><a href="http://moriyoshi.hatenablog.com/entry/2017/03/13/011005">「ユニコード」で予期せぬ目に遭った話 - moriyoshiの日記</a></li>
</ul>
<p><a href="https://text.baldanders.info/golang/bookmark/">Go 言語に関するブックマーク集はこちら</a>。</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>結合文字はひとつの基底文字に対して複数付加される場合もある。しかもこの場合に結合文字同士の順序は不定である。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:2">
<p>正しくは「符号化文字集合(coded character set)」である。 <a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:3">
<p>これは UTF-8 などの「文字エンコーディング」とは異なるものだ。 <a href="#fnref:3" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:4">
<p>日本の JIS 規格にも「歴史的経緯」による重複符号化がある。言わずと知れた「半角」「全角」文字である。異体字も一種の重複符号化と言える。もちろんこれは日本語圏だけの問題ではなく,大抵の言語圏は似たような重複符号化の問題を抱えている。そして Unicode はそうした仕様上の欠陥も含めて併呑してしてしまっているため,このような有様になっているわけだ。問題を先送りして更に酷いことになるという失敗例の典型ですな。この辺の「歴史的経緯」の一部については大昔に拙文「<span><a href="https://baldanders.info/spiegel/archive/charset-pdfa.pdf">文字コードとその実装 <sup><i class="far fa-file-pdf"></i></sup></a></span>」で言及しているので参考にどうぞ。古すぎて一部使えない記述もあるけど。 <a href="#fnref:4" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:5">
<p>単に全角・半角変換ができればいいのなら <code>golang.org/x/text/</code><a href="https://godoc.org/golang.org/x/text/width" title="width - GoDoc"><code>width</code></a> パッケージをお勧めする。 <a href="#fnref:5" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:6">
<p>ちなみに Windows のファイルシステムはフォルダ・ファイルの名前を正規化するとかいうアホなことはしない。事前合成形も合成列も受け入れる。見かけ同じ名前のフォルダ・ファイルが複数できる可能性はあるが,それはそれ。多分,ほとんどの OS のファイルシステムは名前の正規化なんてしてないはず。この HFS+ による Unicode 正規化のおかげで他プラットフォームはかなりの迷惑を被ることになる。たとえば複数のプラットフォームをまたぐファイル交換(例えば Linux → OS X → Windows みたいな経路)を行った場合に OS X を経由した途端にフォルダ・ファイル名を書き換えられてしまうのだ。しかもユーザやアプリケーションは基本的に干渉できない。迷惑千万な話である。もっとも Windows ユーザは Windows ファイルシステムのダメさ加減が身に沁みてるので他所を嗤えないけど(笑) <a href="#fnref:6" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:7">
<p>この記事では最初 NFD-mac と書いていたのだが,どうも巷では UTF-8-MAC というのが一般的らしい。しかしこの件はあくまでも正規化の問題であり文字エンコーディングの問題ではない。なので UTF-8-MAC という呼び名は的を得ていないと思うのだが, Apple 信者ではない私がどうこう言ってもしょうがないので UTF-8-MAC 表記で統一しておく。まぁ符号化文字集合と文字エンコーディングと正規化の区別もつかない馬鹿ばっかりだからこのような愚行を犯してしまったのだろう。 <a href="#fnref:7" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:8">
<p>たとえば Linus Torvalds は HFS+ に起因する git の脆弱性問題で<a href="http://cpplover.blogspot.jp/2015/01/blog-post_14.html">激怒</a>している。 <a href="#fnref:8" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div>