レコードをテキストに変換する方法の一歩先

MacBookiPhoneiPadSafariにおいて、あらゆるページで自動ログイン・自動入力を目指した上記 auto_login*1スクリプトの作成では、その過程で様々な問題が持ち上がった...。

  • 今回のスクリプト作成で最後まで悩んでいたのは、AppleScriptのレコードを文字列に変換する方法だった。
  • ログイン情報をJavaScriptで利用するにはJSON形式に変換する必要があり、どうしても必要な処理だったのだ。
  • ところが、AppleScriptにはレコードを文字列にする方法が提供されていない。(as textしてもエラーになる)
  • AppleScriptエディタでレコードを結果として表示した時は文字列になっているのだけど、
  • それを毎回いちいちコピーして、あそこにペーストしてください、というダサイ仕様には絶対にしたくないと思っていた。
  • にもかかわらず、レコード文字列変換の方法が全く思い付かなかった。
  • 実は最初にauto_loginスクリプトを作り始めた時から、レコード文字列変換はそのうち必要になると思っていた。
  • かれこれ一週間以上、心の片隅で常に悩み続けていたのだけど、一向に閃かない...。
  • GUIスクリプトでコピー&ペーストするか、いやクリップボードの履歴にパスワードが残ってしまうのは問題あり。
  • いっそのこと、レコード使うのやめて、JSONなテキストで管理しようかなんて思い始めていた。
  • そんな悶々とした日々を過ごしていて、ある晩、夜中に突然目が冴えたので「もう一度検索してみるか」と思って検索してみた。すると...
  • まさに求めていた情報で、一気の脳が活性化していくのを感じた!気分がハイになる。
  • 夜中の3時頃だったけど、そこから一気に朝まで作業して動くものを仕上げてしまった。

レコードをテキストに変換する方法


{a:1, b:2} as text

  • 上記を実行するとエラーが発生して、メッセージが表示される。
    • error "{a:1, b:2} のタイプを text に変換できません。" number -1700 from {a:1, b:2} to text
  • ところで、エラーはtryで補足できる。


try
{a:1, b:2} as text
on error msg number num
msg & return & num
end try

  • それぞれの変数には、以下の内容が代入される。
    • msg変数=”{a:1, b:2} のタイプを text に変換できません。”
    • num変数=−1700
  • あれれ?msg変数にはレコードを文字列で表現したものが含まれている!
  • ならば余分な「 のタイプを text に変換できません。」の部分を取り除けばレコードを表現するテキストになるのだ。


on text_from(a_record) try
a_record as text
on error msg
msg's items 1 thru -22 as text
end try
end text_from

text_from({a:1, b:1}) --実行結果:"{a:1, b:1}"

感謝!

素晴らしい!AppleScriptに備わっていない新たな機能、レコード → テキスト変換がこんなに簡単にできてしまった!

  • 例外処理を上手く活用するこの方法は、自分の発想には全くなかった...。
  • AppleScriptの穴 Piyomaru Softwareさんの素晴らしい発想に感服。
    • ちなみに、上記ページのコメント欄にはクリップボードを活用する方法も投稿されている。こちらも素晴らしい発想。
    • 但し、今回はパスワードを扱うので、例外処理で変換する方法で実装した。
    • クリップボード系ユーティリティの履歴に残り続けてしまうことを懸念して。
  • AppleScriptの穴は、AppleScriptについて検索すると、有益な情報として、どこかで必ず絡んでくる素晴らしいサイト。
  • AppleScriptの言語やライブラリとして不足している部分を、独自の発想で、いつも大胆に解決してしまう。
  • そんな情報が豊富なサンプルコードとともに多数紹介されている。
  • サンプルコードがちゃんと動くので、その仕組みも理解しやすい。
  • 自分がAppleScriptでコーディングする時は、いつも、どこかで必ずお世話になる。
    • 今回のように、ピンポイントな解決法が紹介されていたり、
    • サンプルコードから新たな発想を思い付いたり、
    • より効率的なアルゴリズムを見つけたり、等々。

深く深ーく、感謝です!

一般化を目指す

  • ところで、上記で作ったtext_from()ハンドラに、レコード以外の引数を渡したらどうなるだろうか?


on text_from(list_or_record)
try
list_or_record as text
on error msg
msg's items 1 thru -22 as text
end try
end text_from

--サンプルコード -- 実行結果
text_from({a:1, b:2}) --"{a:1, b:2}"
text_from({{a:1}, {b:2}}) -- ""
text_from({1, 2, 3}) -- "123"
text_from("ABC") -- "ABC"
text_from("123") -- "123"
text_from(123) -- "123"

  • 残念ながら、2番目、3番目のリストについては、あまり役立たない形式のテキストに変換されてしまう...。
  • では、「as text」の部分を「as number」に変更し、「1 thru -22」の部分を「1 thru -24」に変更してみる。


on text_from(list_or_record)
try
list_or_record as number
on error msg
msg's items 1 thru -24 as text
end try
end text_from

--サンプルコード -- 実行結果
text_from({a:1, b:2}) --"{a:1, b:2}"
text_from({{a:1}, {b:2}}) -- "{{a:1}, {b:2}}"
text_from({1, 2, 3}) -- "{1, 2, 3}"
text_from("ABC") -- "\"ABC\""
text_from("123") -- 123
text_from(123) -- 123

  • すると、ほぼ理想的な形式のテキストとして、それぞれの値が返って来た!
  • さらに、ここで text_from() ハンドラが返すべき値を、再定義してみると...
    • レコード:{a:1, b:2} テキスト:"{a:1, b:2}"
    • リスト&レコード:{{a:1}, {b:2}} テキスト:"{{a:1}, {b:2}}"
    • リスト:{1, 2, 3} テキスト:"{1, 2, 3}"
  • 以上の変換は理想だと考える。
  • その特徴は、ダブルクォート内側がレコード・リストのソースコードになっていること。
  • そのルールを適用するなら、テキスト・数値についてもダブルクォート内側をそのソースコードとすると一貫性を保てる。
  • それを実現するため、正攻法でifを使って以下のようにしてみた。


on coding(obj) try
obj as number
if obj's class = text then
"\"" & obj & "\""
else
obj as text
end if
on error msg
msg's items 1 thru -24 as text
end try
end coding

--サンプルコード -- 実行結果
coding({a:1, b:2}) --"{a:1, b:2}"
coding({{a:1}, {b:2}}) -- "{{a:1}, {b:2}}"
coding({1, 2, 3}) -- "{1, 2, 3}"
coding("ABC") -- "\"ABC\""
coding("123") -- "\"123\""
coding(123) -- "123"

  • もはや、これはレコード・リストにとどまらず、あらゆるオブジェクトをソースコード化するハンドラとなった。


coding(obj)ハンドラと改名して、今ここに出来上がる!


  • 上記サンプルコードでは変数に代入してないので分かりにくいかも。
  • 以下のように、変数に代入されたオブジェクトでも変換できるのだ。


on coding(obj)
try
obj as number
if obj's class = text then
"\"" & obj & "\""
else
obj as text
end if
on error msg
msg's items 1 thru -24 as text
end try
end coding

set login_info to {https___direct_jp_bank_japanpost_jp_tp1web_U010101SCK_do:{{checked:false, value:"1234", type:"text", |name|:"okyakusamaBangou1", |index|:"3"}, {checked:false, value:"5678", type:"text", |name|:"okyakusamaBangou2", |index|:"4"}, {checked:false, value:"12345", type:"text", |name|:"okyakusamaBangou3", |index|:"5"}}}

coding(login_info)
--結果:"{https___direct_jp_bank_japanpost_jp_tp1web_U010101SCK_do:{{checked:false, value:\"1234\", type:\"text\", |name|:\"okyakusamaBangou1\", |index|:\"3\"}, {checked:false, value:\"5678\", type:\"text\", |name|:\"okyakusamaBangou2\", |index|:\"4\"}, {checked:false, value:\"12345\", type:\"text\", |name|:\"okyakusamaBangou3\", |index|:\"5\"}}}"

言語環境に依存しないバージョン

  • 正規表現を利用して取り出し、日本語環境以外でも利用できるようにしてみた。(英語環境でテスト)
  • シェルの262,144バイト制限を考慮して、日本語環境でのみ救われるように対応してみた。


on coding(obj)
try
if obj's class = text then
"\"" & obj & "\""
else if obj's class = list or obj's class = record then
obj as number
else
obj as text
end if
on error msg
try
"require \"jcode\";$KCODE=\"u\";/\\{.*\\}/=~" & quoted form of msg & ";puts($&);" --rubyコード
do shell script "ruby -e " & quoted form of result --シェルコード
on error
--do shell script262,144バイト制限エラーでも、日本語環境なら救われる
--http://developer.apple.com/jp/technotes/tn2002/tn2065.html
msg's items 1 thru -24 as text --日本語環境以外ではNG
end try
end try
end coding

--サンプルコード -- 実行結果
""
result & return & coding({a:1, b:2}) --"{a:1, b:2}"
result & return & coding({{a:1}, {b:2}}) -- "{{a:1}, {b:2}}"
result & return & coding({1, 2, 3}) -- "{1, 2, 3}"
result & return & coding("ABC") -- "\"ABC\""
result & return & coding("123") -- "\"123\""
result & return & coding(123) -- "123"
result & return & coding(true) -- "true"
result & return