2009年9月12日土曜日

do-applescript 専用の文字列リテラル関数

Carbon emacsには、emacsから applescriptを実行する関数として do-applescript というのがある。とても便利なのだが、applescript に文字列リテラルを渡す時のエスケープ処理が面倒なので、 専用の関数 applescript-string-literal を作ってみることにした。
つまり、
(do-applescript "display dialog \"any-string\"")
の赤色の部分を作成する関数だ。
(do-applescript (format "display dialog %s" 
                    (applescript-string-literal "any-string")))
のように使う。

最初は、単にバックスラッシュ使って、 ダブルクォートとバックスラッシュ自身をエスケープするだけでいいと思っていたのだが、 leopardの文字列処理はそう甘くはなかった。

どこが甘くなかったのか.....
例えば、
(do-applescript "display dialog \"Hello\"")
はちゃんと、Helloというメッセージダイアログを表示するのだが、 Helloを、"Hello" にかえて、
(do-applescript "display dialog \"\\\"Hello\\\"\"")
を実行すると、何故かエラーになってしまう。 バックスラッシュ(\)によるダブルクォート(") のエスケープ処理が効いていないみたいだ。

これは、バックスラッシュと円記号 にまつわるややこしい問題なのかと思い、試しに
(do-applescript "display dialog \"Hello\\\"")
を実行。すると、 Hello\ ではなく、 Hello と表示された。
スクリプトエディターで、\のコードを確認すると、
ASCII NUMBER "\\" は 128
ASCII NUMBER "¥" は 92
と評価される。(実際には¥はoption-\で入力)
ということは、 do-applescriptで送るバックスラッシュコード(asciiの92) が、applescript では半角の¥記号と認識され、 それで文字列リテラルのエスケープが効かないのか?
確かに、applescriptの ascii character 128 を使って、
(do-applescript "display dialog \"Hello\" & ascii character 128")
を実行すれば、正しく Hello\ と表示される。
めんどくさいけど、 バックスラッシュを ascii character 128 に、 ダブルクォートを ascii character 34 にして、それらと文字列リテラルを & でくっつければいいらしい。

でも、以前は確かこんな症状は起きなかったような気がする。 僕自身が何か変な環境設定でも行ったのだろうか....
調べてみると、環境変数 __CF_USER_TEXT_ENCODING が関係していることがわかった。この環境変数を UTFエンコードを表す 0x08000100 に設定して、
(例 : export __CF_USER_TEXT_ENCODING=`printf 0x%X $UID`":0x08000100:14")
emacsを再起動すると、
(do-applescript "display dialog \"\\\"Hello\\\"\"")
も正常に動作し、これまでの問題は全て解決する。しかし今度は日本語が化けてしまう。 デフォルトの $UID:1:14では shift_jisでエンコードすれば問題なかったのに......

結局、 __CF_USER_TEXT_ENCODING が ShiftJisの場合は、文字列リテラルと ascii character 文の結合で処理し、 それ以外の場合は単純なエスケープ処理を施すように applescript-string-literal 関数を実装することにした。

applescript-string-literalソースコード
(cond
 ((and (<= emacs-major-version 22)
       (string-match
        ".*:1:14" (format "%s" (getenv "__CF_USER_TEXT_ENCODING"))))

  ;; emacs22 and CFUserTextEncodingが MacJapanese の場合
  (defun applescript-string-literal (str)
    "do-applescriptに渡す文字列リテラルを作成\n\
        バックスラッシュとダブルクォーテーションをそれぞれ\n\
        ascii character 128 と ascii character 34に変換\n\
        ex. (applescript-string-literal \"\\\"abc\\\"\")\n\
        => \"ascii character 34 & \\\"abc\\\" & ascii character 34\""
    (let ((reslst '()))
      (mapc
       '(lambda (ch)
          (cond ((= ch ?\\) (setq reslst (cons 128 reslst)))
                ((= ch ?\") (setq reslst (cons 34 reslst)))
                ((consp (car reslst))
                 (setcar reslst (cons ch (car reslst))))
                (t (setq reslst (cons (list ch) reslst)))))
       str ;; (append str nil)
       )
      (if (null reslst) "\"\""
        (mapconcat
         '(lambda (x)
            (if (consp x)
                (concat "\"" (reverse x) "\"")
              (format "ascii character %d" x))
            ) (reverse reslst) " & ")) 
      )))
 (t ;; emacs23 or CFUserTextEncodingが MacJapanese 以外 の場合
  (defun applescript-string-literal (str)
    "do-applescriptに渡す文字列リテラルを作成"
    (concat "\""
            (replace-regexp-in-string ;; convert " => \"
             "\\\"" "\\\\\""
             (replace-regexp-in-string ;; convert \ => \\
              "\\\\" "\\\\\\\\"
              str)) "\""))))

この applescript-string-literal を使って、
(do-applescript
  (format "display dialog %s"
     (applescript-string-literal "Hello \"backslash\"-\\")))
を実行すると、めでたく Hello "backslash"-\ が表示される。

__CF_USER_TEXT_ENCODINGがデフォルトのShiftJisの状態なら、 日本語だって、
(do-applescript
  (format "display dialog %s"
    (encode-coding-string
      (applescript-string-literal "バックスラッシュ \"\\\"を表示")
      'shift_jis)))
ちゃんと バックスラッシュ"\"を表示 のダイアログが表示される。

.... でも、本当は何かもっとスマートな方法があるんだろなあ....

追記  (2009年 11月4日 水曜日)
cocoa emacs (emacs23.1) の場合は、ascii characterへの変換は必要なく、 日本語もエンコードせずにそのまま送ればいいようだ。

0 件のコメント:

コメントを投稿