こんにちは。ざわかける!のざわ(@zw_kakeru)です。
Androidアプリ開発において、アプリ名に‘(アポストロフィ)を入れようとした時のエスケープでハマったので、その時のTipsです。
動作環境
macOS: 11.2.2 (Big Sur)
Flutter: 2.0.1
Dart: 2.12.0
Visual Studio Code: 1.53.2
今回、問題が発生したアプリ『Tappin’Timer』はこちらです。
日本語名は『タップタイマー』になっています。
iOS版はこちらです。
起きたこととやったこと(error: unescaped apostrophe in string)
アプリ名を指定するファイルandroid/app/src/main/res/values/strings.xmlにアクセスし、アプリ名Tappin’Timerの入力を行いました。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Tappin'Timer</string>
</resources>
保存して実行したところ、以下のようなエラーが出ました。
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:mergeDebugResources'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
> Android resource compilation failed
/hoge/android/app/src/main/res/values/strings.xml:3:3-48: AAPT: error: unescaped apostrophe in string
/hoge/android/app/src/main/res/values/strings.xml:3:3-48: AAPT: error: not a valid string.
/hoge/build/app/intermediates/incremental/mergeDebugResources/merged.dir/values/values.xml: AAPT: error: file failed to compile.
見るとerror: unescaped apostrophe in stringの文字。
アポストロフィのエスケープを行わないとダメなようですね。
(は〜い、了解っす〜。)と思いながらヒョヒョイとエスケープしてやりました。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Tappin'Timer</string>
</resources>
保存して実行。
しかし、なぜかエラーメッセージに変化はなく、変わらずerror: unescaped apostrophe in stringの記載が。
嘘やん、ちゃんとエスケープしたって。(これで解決していたらこんな記事書かずに済んだのに、、)
エラーメッセージをよく見ると実際にコンパイルエラーが起きているのは/build/app/intermediates/incremental/mergeDebugResources/merged.dir/values/values.xmlという別のxmlファイルであるらしく、そちらを確認してみました。
...// (省略) //...
<string name="app_name">Tappin'Timer</string>
...// (省略) //...
なるほど、どうやらstring.xmlでのエスケープは正常に行われていますが、アプリのビルド時に中間生成ファイルとしてこのvalues.xmlが作成されるようで、その際にエスケープが戻された結果、values.xml側で再びエスケープのエラーが出たようですね。
原因は分かりましたが、これってどう対応してあげてれば良いんですかね。
xmlから一旦別のxmlに変換されるならエスケープしても無駄じゃん、、、となり、試しにstring.xmlをこう書き変えてみました。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Tappin&apos;Timer</string>
</resources>
&(アンド:amp)もエスケープさせてvalues.xmlに渡す作戦!
こうすればstring.xml からvalues.xmlに渡る際にTappin'Timerとなり、画面出力時にはここから正しくエスケープが戻されてTappin’Timerになるじゃないか。天才。
実行してみた結果見事エラーは消え、出力が次のようになりました。
いやこれ&(アンド)はちゃんとエスケープされてるけど結局'(アポストロフィ)はできてないやないかーい。
(なんでこうなるの?)と思ってvalues.xmlを覗いてみたら、衝撃の事実が判明。
...// (省略) //...
<string name="app_name">Tappin&apos;Timer</string>
...// (省略) //...
なんと、&(アンド)は'(アポストロフィ)と違い、values.xmlの段階ではエスケープが戻されていなかったのです!
(なんで!? アポストロフィの時はvalues.xmlの時点で戻されてたじゃん!イジメか!?私がやりたいことをさせないための仕様か!?)と思ながら、ここでお手上げしました。
原因と対処
結局根本的な原因は分かりませんでしたが対処法としてStackoverflow先生に聞くと、xmlのパース時には完全に素通りしてもらって、実際に文字列を出力する際に戻してもらえばそれで良いよ、とのことです。
具体的には、\(バックスラッシュ)でエスケープを行うと解決するとのことです。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Tappin\'Timer</string>
</resources>
これでいけました。
でもこれも結局対症療法でしかないんだよなあ。
つまりxmlでのエスケープでは解決することができないってことですよね。
ふざけんなよ〜。
(追記:values.xmlの中身をよく見たらデフォルト設定されたメッセージ内に'(アポストロフィ)を使っている部分が数カ所あり、そこでもやはり\(バックスラッシュ)でエスケープがなされていました。)
終わりに
今回の問題は完全にライブラリ側のバグだと思っているのですがどうなんでしょうか。
&(アンド)のエスケープはできるのに'(アポストロフィ)はできないなんて欠陥ですし、問題を余計にややこしくさせていますよね。
最初から同じエラーが出されていたら、もっと早くこの解決策に辿り着けたと思います。
この問題はどうも数年前(確認できたもので7年前)から存在しているらしく、早く直してくれよ、、、と思わずにはいられませんでした。
「システム側の問題やろ、ふざけんなよ〜。」という感情に任せてこの記事を書き上げました。