生のbinaryと言うからにはBase64に変換するような生ぬるい方法ではない。ほぼそのまま埋め込むのだ。ほぼとは何ぞ? と思う読者もいるだろう。1つだけ除外しなければならない文字が存在するのだ。それがUnicode番号13、復帰文字という奴だ。
厄介な事にhtmlに記述される文字の中でも復帰文字はUnicode番号10として認識されてしまう。環境にもよるかもしれないが、少なくともWindows Google Chrome、JavaScriptでは innerText、innerHTML、textContent
で得られる値は10である。そのため別の文字に変換した上でescape文字と組にしなければならない。当然だがescape文字自身もescape文字と組にしなければならない。
単純に \
でescapeすれば良いという話ではない。そもそもJavaScriptのように \r
と書いても復帰文字が自動召喚されるわけではない(まあJavaScript側でそういう風に処理すればいいのだが…)。
escape文字に適任なのはbinary data中で最も出現数が少ない文字である。その方が膨張率が小さくて済む。
さて、下らないかもしれない前置きはこの辺にして本題とやらに話をにすり替えていこう。
制約
こんな馬鹿げた事をする為にはmeta要素で charset=l1
(latin1、iso-8859-1等と同義)を設定しなければならない。UTF8などという符号化法式を選択する余地はないのだ!
html中のどこに仕込むか?
さすがにどこでもいいというわけにはいかない。a要素やp要素では意味不明な文字列がだらだらと丸出しになる。おまけに連続する空白類も1文字分扱いだ。要素内の<!-- -->
の区間の文字は全て無視される始末。これらを考慮すると候補は限られる。
- style
偶然cssとして解釈されるbinary dataに直面するかもしれない。その対策としてtype="text/plain"
を設定すれば良い - script
type="text/plain"
を設定する - noscript
特に欠点が無いかも - textarea
開始tag直後と閉じtag直前の改行が無視される問題がある。加えて数値文字参照と文字実体参照の対策は非常に面倒 - xmp
廃止された要素。それを無視すれば使い勝手が良い。<xmp hidden>...</xmp>
などと記述すれば中身は隠蔽される…かも - plaintext
廃止された要素。最強っぽいけどhtml最後の要素にする必要がある
plaintext以外はbinary data中に閉じtagと認識できる羅列が見つかったら破綻する。その対策として閉じtagとみなせる羅列の途中にescape文字を仕込む。
html編集にはBinary Editor
文字番号0のLatin1文字は普通の文書編集programでは消えていたり別文字に変換されている可能性がある。安全を期するためにもBinary Editorで編集する事を勧める
復元処理
latin1の文書の中でも文字番号128~159(130,143,144,146,158以外)の扱いは特殊である。この連中は charCodeAt
で得られる値が変態じみている。詳細は以下の表の通り。
latin1 | 取得値 | latin1 | 取得値 | latin1 | 取得値 |
---|---|---|---|---|---|
128 | 376 | 138 | 8226 | 150 | 8240 |
129 | 382 | 139 | 8221 | 151 | 710 |
131 | 339 | 140 | 8220 | 152 | 8225 |
132 | 8250 | 141 | 8217 | 153 | 8224 |
133 | 353 | 142 | 8216 | 154 | 8230 |
134 | 8482 | 145 | 381 | 155 | 8222 |
135 | 732 | 147 | 338 | 156 | 402 |
136 | 8212 | 148 | 8249 | 157 | 8218 |
137 | 8211 | 149 | 352 | 159 | 8364 |
これらのひねくれ者的な文字に対しては、文字番号通りの値を得るための特殊な処理が必要だ。例えば以下のようになる。
A=Uint8Array.form(L1txt,(c,i)=>(i=c.charCodeAt()%65533)>>8?129+' \x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C \x8E \x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C \x9E\x9F'.indexOf(c):i)
\x82\x83...\x9E\x9F
の部分は実際には生のLatin1文字でいいのだが、便宜上このように記述。%65533
とは何者か? charCodeAt
は文字番号0のLatin1文字を65533だ! などと豪語しておられるのだ。その誤解を解くためのまじないである。
連想配列で紐付ける古典的な手法もある。以下の例ではnoscript
要素の中身をbinaryに変換して取得している。
<noscript id=s>abc ABC
!"#$%\~:;</noscript>
<script>
function L1toBin(L1txt){
var a=256,z=L1txt.length,h={__proto__:null},A=new Uint8Array(z);
for(;a;)h["\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f".charAt(h[String.fromCharCode(--a)]=a)]=a^128;
for(;a<z;)A[a]=h[L1txt.charAt(a++)];
return A
}
A=L1toBin(s.innerText)
</script>
これも\x80\x81...\x9e\x9f
の部分は生のLatin1文字列であり、128 bytesとなる。え? charAt
は古臭いって? そう、今時は[]
で位置を指定できるのだが、古風にこだわってみた。
7行目も今時は以下のように書き換えられる。
for(z of L1txt)A[a++]=h[z];
裏技
実は閉じtag、<!-- -->
、復帰文字、escape文字等といったものを考慮せず埋め込む方法がある。htmlをpng形式の画像として再読み込みすればいいのだ。少し特殊でやや複雑な方法であり、制約もある。
htmlの先頭にpngの頭情報を埋め込み、続いて画像本体、htmlのcanvas要素とimg要素を埋め込む。png画像本体は埋め込みたい任意のbinaryから構築する。
img要素のonload属性でアレコレ処理すると、任意のbinary情報を取得できるって寸法だ。実用例としてzpngがある