りんご競争 (3)

一日のアクセス数が少し増えてます。なぜか?アクセス解析などしていないので何が原因か分からないんですが。

さて、りんご競争 (1) は導入編でした。りんご競争 (2) で、ゲームの初期化の部分を少し調べたのですね。文字属性の変更で終わっています。

少し話はそれてしまうのですが、ここで TextEdit とスクリプティング対応エディタとの差異、TextEdit での文字列の操作についてみておきます。まず、 TextEdit とスクリプティング対応エディタとの一番の違いは、insertion point の有無と選択文字列の取得が可能かどうかにあります。TextEdit は、insertion point も select 命令も selection も利用できません。ですので、選択している文字列の前後に HTML タグをつけるなどという操作がとてつもなく難しい作業だったりします。しかし、UI Scripting を使えば以下のように可能です。

Script Editor で開く

tell application "TextEdit" to activate

tell application "System Events"
    tell process "textedit"
        keystroke "c" using {command down}
        my addtag("P")
        keystroke "v" using command down
    end tell
end tell

on addtag(tagStr)
    set clipContent to «class ktxt» of ((the clipboard as text) as record)
    set clipContent to "<" & tagStr & ">" & clipContent & "</" & tagStr & ">"
    set the clipboard to clipContent
end addtag

この場合、タグで囲みたい文字列を TextEdit で選択しておく必要があります。選択範囲をどうにかしたいというときは、このように clipboard 経由で文字列を加工するのが最も簡単だと思われます。UI Scripting を利用しなくても、clipboard 経由なら文字列をコピーしておくことで操作が可能です(コピー/ペーストは手作業で)。他のアプリケーションなどとの連携も clipboard を中継させることがよくあるので、調べておくと何かの役に立つかもしれません。

選択されている文字列以外ではどうでしょうか?例えば、全部の行に何らかの処理を行いたい、というとき。TextEdit は、character と word、paragraph が利用できます。用語辞書を見ると分かりますが、これらのオブジェクトは、document クラスの text 属性を通して操作します。

Script Editor で開く

tell application "TextEdit"
    last character of paragraphs of text of front document
end tell

このスクリプトを実行すると分かりますが、行の最後の文字は改行です。ですので、単純に次のようなスクリプトを実行すると改行は失われてしまいます。

Script Editor で開く

tell application "TextEdit"
    set paragraph 1 of text of front document to "何らかの文字列"
end tell

これを踏まえて各行にタグを追加するとなると、以下のようにします。

Script Editor で開く

property LF : ASCII character 10

tell application "TextEdit"
    tell text of front document
        set last character of paragraphs of it to "<br />" & LF
    end tell
end tell

最後の改行文字を変更しているだけです。では、各行の最初に追加するときは?これが少し面倒です。間違っても以下のようにしてはいけません。

Script Editor で開く

tell application "TextEdit"
    tell text of front document
        set first character of paragraphs of it to "<p>" & first character of paragraphs of it
    end tell
end tell

これだと、各行の先頭に <p> + 各行数分だけ先頭の文字が追加されてしまいます。ちなみに以下のようにしても動きません。

Script Editor で開く

tell application "TextEdit"
    tell text of front document
        set paragraphList to a reference to paragraphs of it
        repeat with thisParagraph in paragraphList
            set contents of thisParagraph to "<p>" & thisParagraph
        end repeat
    end tell
end tell

ふむ、なかなか難しいです。次のようなスクリプトもエラーになります。

Script Editor で開く

tell application "TextEdit"
    tell text of front document
        set before first character of paragraph 1 of it to "<p>"
    end tell
end tell

単純に一文字目を置き換えるだけなら簡単なんですが。なら、make 命令で作ってしまいましょう。

Script Editor で開く

property LF : ASCII character 10

tell application "TextEdit"
    tell text of front document
        set n to count paragraphs of it
        repeat with i from 1 to n
            make new word at before first character of paragraph i of it with data "<p>"
            if i is n then
                make new word at after last character of paragraph i of it with data "</p>"
                return
            end if
            make new word at after character -2 of paragraph i of it with data "</p>"
        end repeat
    end tell
end tell

この他にもスクリプトで文字列を作って、最後に text 属性を全て置き換えてしまう、という方法もありますね。

Script Editor で開く

tell application "TextEdit"
    tell text of front document
        set paragraphList to paragraphs of it
        set tmp to ""
        repeat with i from 1 to length of paragraphList
            set thisParagraph to item i of paragraphList
            set tmp to tmp & (i as Unicode text) & " : " & thisParagraph
        end repeat
    end tell

    set text of front document to tmp
end tell

こちらの方が細かい変更ができていいかも知れません。もちろん、これら以外にも text item delimiters を使う方法もありますし、do shell script 経由で Perl 等に処理してもらう方法もあります。

では、文字列中の特定の文字を変更したい場合はどうでしょう?いわゆる検索、置き換えなんですが。TextEdit のようなスクリプタブル Cocoa アプリケーションは、ほとんどのクラスでフィルタ参照が利用できます。

Script Editor で開く

tell application "TextEdit"
    tell text of front document
        characters of paragraphs of it whose it is "文"
    end tell
end tell

このようにすることで各行中の「文」の文字だけを抜き出すことができます。もちろん、以下のようにして置き換えることも可能です。

Script Editor で開く

tell application "TextEdit"
    tell text of front document
        set characters of paragraphs of it whose it is "文" to "愛"
    end tell
end tell

ほぼ一瞬で置き換えは終わります。しかし、落とし穴があります。この例の場合は、「文」を「愛」に置き換えました。例えば、「文」を「学校」のように一文字を二文字に置き換えたりすると、位置の参照が変わってしまい(最初の文字を置き換えたところで以降の文字は一文字ずつてしまい)正しく置き換えることができなくなります。では、このようなフィルタ参照は使えないのか?というと、そうではなくてこのフィルタ参照は文字の属性を変更するときに大いに役立ちます。

例えば、普通のテキストを RTF にして見栄えを整えたいとき。RTF で文字に属性がついているときに文字サイズが 24 ポイントのものを 18 ポイントに変更したいとき。こういうときにフィルタ参照を利用すると効率よく処理できます。フィルタ参照の条件指定を大いに利用すれば、かなり細かい指定ができます。まぁ、リストや行間、スタイルやセンタリング等の位置指定が利用できないのはどうにもなりませんが。

スクリプタブル Cocoa アプリケーションの特徴は、このフィルタ参照です。繰り返しを行うより、フィルタ参照で一括処理を行う方が速いです。もう一つの特徴は、a reference to 〜 による参照です。

Script Editor で開く

tell application "TextEdit"
    set attrList to attribute runs of text of front document
    repeat with thisAttr in attrList
        if font of thisAttr is "Osaka" then
            set font of thisAttr to "Optima-Regular"
            set size of thisAttr to 24
            set color of thisAttr to {12548, 5874, 35694}
        end if
    end repeat
end tell

このようにして文字の属性を変更しようとしてもエラーになります。これは、attrList におさめられているのがドキュメント内の文字列に対する参照ではないからです。以下のように参照にすることできちんと動くようになります。

set attrList to a reference to attribute runs of text of front document

ほとんどの場合スクリプタブル Cocoa アプリケーションで複数の項目を取得すると適切な参照になります。

Script Editor で開く

tell application "Mail"
    messages of mailbox 1
    properties of item 1 of result
end tell

tell application "iCal"
    todos of calendar 1
    properties of item 1 of result
end tell

tell application "TextEdit"
    paragraphs of text of front document
    properties of item 1 of result --エラーになる
end tell

Mail、iCal は、それぞれ id 参照と番号参照で項目を返しますが、TextEdit は、中身そのもの(つまり、文字列)を返します。アプリケーションにより異なるのが困るのですが、このように a reference to 〜 を用いて参照を利用する場合が時々あります。しかし、だからといってどんなオブジェクトも a reference to 〜 で取得するといいのかというとそうではありません。オブジェクトによってはその後の処理が著しく遅くなることがあります。

Script Editor で開く

tell application "Mail"
    set messagesRef to a reference to messages of mailbox 1

    repeat with thisMessage in messagesRef
        subject of thisMessage
    end repeat
end tell

このようにするよりも、次のように素直(?)に書く方が速いです。

Script Editor で開く

tell application "Mail"
    set messagesRef to messages of mailbox 1

    repeat with thisMessage in messagesRef
        subject of thisMessage
    end repeat
end tell

処理速度を稼ぐために次のようなスクリプトを書くことがあります。

Script Editor で開く

tell application "Mail"
    set messageList to messages of mailbox 1

    set theList to {}
    set theListRef to a reference to theList
    repeat with thisMessage in messageList
        set end of theListRef to subject of thisMessage
    end repeat
end tell

しかし、これは速度的に違いはありません。a reference to 〜 は、使う場面さえ間違えなかったら速度の向上が見込めますが、多くの場合 、普通にスクリプトを書くのとそれほど変わりがなかったりします。

Script Editor で開く

tell application "System Events"
    set prefsFolder to preferences folder of user domain
    set prefsFile to path of (disk item "com.apple.itunes.plist" of prefsFolder)
    set prefsFile to property list file prefsFile

    set propList to a reference to property list items of prefsFile
    --set propList to property list items of prefsFile
    set theList to {}
    set theListRef to a reference to theList
    repeat with thisItem in propList
        set end of theListRef to name of thisItem
        --set end of theList to name of thisItem
    end repeat
end tell

コメントアウトしている部分のコメントを外しても a reference to 〜 を使った場合と比べて、遜色はないです。不必要に a reference to 〜 を使うより、他の方法を模索する方が結果的に速い場合があります。

と、ここまで書いてきて Intel Mac ではどうなるんだろうと思ってしまいました。...気分、萎えたので終わり。

0 件のコメント :

コメントを投稿