02/03 近似式によるRGB→CMYK/CMYK→RGB変換

AppleScriptでRGBの色データをCMYKの色データに変換する手段といえば、Color Sync Scirpingを用いるのが常套手段だったのですが、これがMac OS X 10.6で廃止になりました(残念!)。

・OS X 10.7, LionのRelease Noteに書かれていない変更点
http://piyocast.com/as/archives/1751

ほかに、色データの変換を行う手段といえば、安直にPhotoshopに計算してもらうというものもあります。こちらは、現在でも(Photoshopが入っている環境であれば)利用できます。

・与えられたテキストをPhotoshopでRGBデータと評価してCMYK値を返す
http://piyocast.com/as/archives/1898

ほかには、AppleScriptObjCが使えれば、NSColorのgenericCMYKcolorSpaceを用いて変換することも可能です。genericなCMYKcolorSpaceだけでなく、出力デバイス名を指定して出力特性を反映させて変換を行うことも可能です(genericしか試してないけど)。

……で、これらの手段が使えない場合にどうするかといえば、RGB→CMYK/CMYK→RGBの変換を行う「近似式」があるため、それを使うのが一般的です。

・ISP imaging-developers-色変換式集 - CMYK
http://image-d.isp.jp/commentary/color_cformula/CMYK.html

この近似式を用いた(Objective-Cで書かれた)プログラムの処理結果が思わしくないということだったので、AppleScriptで近似式を用いて相互変換の処理を書いてみました。

RGB→CMYK変換では、黒に近い色(というか黒)を与えると0による除算が発生したり、マイナスの値になったりするケースが見られたため、try〜end tryでエラーをキャッチしたりするなど、地道な補正を加えています。

本Scriptでは、choose colorコマンドで指定した色(RGB)を一度CMYKに変換し、変換したCMYK値を再度RGB値に変換してchoose colorコマンドで色プレビューを行っています。

RGB→CMYKの変換時に発生する一種の「くすみ」がなく、見たまんまそのまま変換されてしまうので、分る人が見るとウソだと分るのですが、単に変換したいだけという場合には割と使えるという感触です。

スクリプト名:指定色をRGB→CMYK→RGB変換プレビュー
–色選択
set aCol to choose color

set {rNum, gNum, bNum} to aCol

set rNum to rNum div 255
set gNum to gNum div 255
set bNum to bNum div 255

–RGB→CMYK変換
set {cNum, mNum, yNum, kNum} to approximateRGBtoCMYKconvert(rNum, gNum, bNum) of me

–CMYK→RGB変換
set {rNum2, gNum2, bNum2} to approximateCMYKtoRGBconvert(cNum, mNum, yNum, kNum) of me

set rNum2 to 65535 * rNum2
set gNum2 to 65535 * gNum2
set bNum2 to 65535 * bNum2

–色プレビュー
choose color default color {rNum2, gNum2, bNum2}

–近似式ベースのRGB→CMYK変換
–参照資料:
–http://image-d.isp.jp/commentary/color_cformula/CMYK.html
on approximateRGBtoCMYKconvert(r, g, b)
  
  
set rNum to r / 255
  
set gNum to g / 255
  
set bNum to b / 255
  
  
set nList to {1 - rNum, 1 - gNum, 1 - bNum}
  
  
set kNum to minimumFromList(nList) of me
  
  
–kNum(Black)がマイナスになるケースがあるため、値を補正
  
if kNum < 0 then set kNum to 0
  
  
  
–0による除算が発生した場合の算術エラーに対処
  
try
    set cNum to (1 - rNum - kNum) / (1 - kNum)
  on error
    set cNum to 0
  end try
  
  
–0による除算が発生した場合の算術エラーに対処  
  
try
    set mNum to (1 - gNum - kNum) / (1 - kNum)
  on error
    set mNum to 0
  end try
  
  
–0による除算が発生した場合の算術エラーに対処  
  
try
    set yNum to (1 - bNum - kNum) / (1 - kNum)
  on error
    set yNum to 0
  end try
  
  
  
–C/M/Yがマイナスになるケースがあるため、値を補正
  
if cNum < 0 then set cNum to 0
  
if mNum < 0 then set mNum to 0
  
if yNum < 0 then set yNum to 0
  
  
  
return {cNum, mNum, yNum, kNum}
  
end approximateRGBtoCMYKconvert

–近似式ベースのCMYK→RGB変換
–参照資料:
–http://image-d.isp.jp/commentary/color_cformula/CMYK.html
on approximateCMYKtoRGBconvert(c, m, y, k)
  
  
set rNum to 1 - (minimumFromList({1, c * (1 - k) + k}))
  
set gNum to 1 - (minimumFromList({1, m * (1 - k) + k}))
  
set bNum to 1 - (minimumFromList({1, y * (1 - k) + k}))
  
  
return {rNum, gNum, bNum}
  
end approximateCMYKtoRGBconvert

–最小値を取得する
on minimumFromList(nList)
  script o
    property nl : nList
  end script
  
  
set min to item 1 of o’s nl
  
repeat with i from 2 to (count nList)
    set n to item i of o’s nl
    
if n < min then set min to n
  end repeat
  
return min
  
end minimumFromList

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

01/30 AppleScriptObjCのメモリー浪費現象を解決?!

AppleScriptObjCで作成したプログラムが、実際に作業で使ってみたらメモリを馬鹿喰いして困る、という話を海外でよく見かけます。

自分も、1,000色ほどの色セットと任意の色の近似色を求めるAppleScriptObjCのプログラムを作成し、任意の1色の近似色検索を行う分には問題なかったものの……まとめて数十〜数百色を処理しようとしたところ、MacBook Pro搭載の8GBのメモリーをアレヨアレヨという間に喰いつぶされてしまいました。

その時には急いでいたので、根本的な解決をあきらめ……近似色検索処理だけを通常のAppleScriptのアプレットに追い出し、AppleScriptObjCのプログラムからAppleScriptアプレットをtell文で呼び出すように処理変更。

余計なプロセス間通信を行うので、余計に時間がかかるようになりましたが、メモリを喰いつぶしてスワップしまくるよりはマシという判断でした。事実、プロセス分離を行う前よりもずいぶん処理速度が落ちたのですが……背に腹はかえられません。

後日、時間に余裕があるときに調査を行ったところ……以前に掲載したCocoaベースのソートルーチンが問題の発生源であることが判明(わかってたけど)。

その場でプログラムから作成したオブジェクトについては、ループ処理などで後続の処理がすぐに発生した場合……自動的にはメモリー上から解放されないということが分りました。

ソートルーチンはサブルーチン化しておいたので、AppleScript的にはローカル変数などで処理される安全な「離れ小島」ですが、Cocoaアプリケーション的には「離れ小島ではない」という状態だと理解しました。

解決策は、作成したオブジェクトを明示的にリリース(release)すること。ほんの短い追加ではあるものの、数百件のデータを処理してもまったくメモリーを馬鹿喰いすることなく、処理を継続できました。

このあたり、Info.plistの記述を書き換えると回避できる問題なのかどうか、もうちょっと調べる必要がありますが……

スクリプト名:Cocoaで入れ子のリストを昇順ソート v2(AppleScriptObjC)
set aRecList to {{aName:“ひよこさん”, favoriteFood:“やきそば”, moneyInWallet:30}, {aName:“ぴよこ”, favoriteFood:“やきうどん”, moneyInWallet:20000}}
set sortedList to cocoaSortListAscending(aRecList, “moneyInWallet”)

–Cocoaで入れ子のリストを昇順ソート v2(AppleScriptObjC)
–連続して呼び出した場合にメモリを馬鹿喰いする現象に対処
on cocoaSortListAscending(theList, keyItem)
  
  
– make unique set
  
tell current application’s NSSet to set theSet to setWithArray_(theList)
  
  
– define sorting
  
tell current application’s NSSortDescriptor
    set theDescriptor to sortDescriptorWithKey_ascending_(keyItem, true)
  end tell
  
  
–sort
  
set sortedList to theSet’s sortedArrayUsingDescriptors_({theDescriptor})
  
  
—————————————————————————————
  
theSet’s release –** Important!! ** 重要!!
  
—————————————————————————————
  
  
return sortedList
  
end cocoaSortListAscending

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

01/21 iBooks AuthorはAppleScript非対応

iBook Storeで配布できるePub書類iBooks書類(Fair PLAYのDRMが施されている)を作成できるアプリケーション、iBooks AuthorがMac App Storeで無料公開されました。

author1.jpg

さっそく、iBooks AuthorのAppleScript対応度を確認すべく、AppleScriptエディタでiBooks Authorのアプリケーションアイコンをオープンして確認したところ……

author2.jpg

一応、AppleScript用語辞書が表示されるのですが、アプリケーションの機能と用語辞書の内容が対応していませんし、makeやsetという命令があったとしても、操作する対象(オブジェクト)の定義がほとんどありません。

実際に、新規書類の作成などそれらしい書き方で試してみたのですが、書類の番号のみインクリメントされるばかりで実際には表示されませんでした。

以上の状況から総合的に判断して、残念ながらiBooks Authorの最初のバージョンはAppleScriptにはまともに対応していない状態であると判定。現状では、GUI Scriptingで強引にメニューを操作する以上のことは何もできません。GUI Scriptingで強引に操作を行って、その内容が正しく内部機能へのアクセス経由で検証できないと「結果がどうなっているか分らないけれどとりあえず操作してみた」という無責任なプログラムになってしまいます。GUI Scriptingの併用は、データへの影響を検証しながら慎重に行うべきものです。

また、一部で「JavaScriptで(内部)機能を呼び出せる」といった噂もありましたが、あくまで「HTML5.0+JavaScriptから構成されるウィジェットをePub書類iBooks書類に埋め込める」というだけのものであり、JavaScriptからiBooks Authorの内部機能を呼び出せるわけではありません。

01/14 ドロップされたASをTextWranglerでdiff表示

ドロップされたAppleScriptをTextWrangler(version 3.5.3)でdiff表示するAppleScriptです。他のAppleScriptのファイルを処理してdiff表示(差分表示)を行うユーティリティ的なものです。

以前に、「ドロップされたASをdiff表示 Mac OS X 10.4対応版」というものを作ったことがありました。AppleScriptをdiff表示するのに、普通はApple純正のFileMergeを使っているのですが、そのためには文字コードなどの書き換えをする必要があり、TextWranglerを併用して……TextWrangler自体がdiff表示機能を持っていることを思い出し、TextWranglerでdiff表示させるというものでした。

すでに決着が付いたかのように思われていた、AppleScript業界の「diff問題」が再燃したのは、読者がいるんだかいないんだかさっぱり不明なこのBlogの、読者の方からの1通のメールからでした。

『いつもお世話になります。(サイトに)

希望といいますか要望だけで申し訳ないのですが、「ドロップされたASをdiff表示 Mac OS X 10.4対応版」の10.6対応版などを作ってはいただけないでしょうか?
というのも、10.6ならdiff表示v4で十分なはずなんですが、業務PCにてXcodeのインストールが許可されていないという事情がありまして、もしよろしければ、ご検討いただけますようお願いいたします。』

……「スクリプトエディタ」を操作していたところを「AppleScriptエディタ」に書き換えれば瞬殺ではないか、と思って手をつけてみたら……意外とたいへんでした(汗)

Mac OS X 10.6上でTextWranglerを使ってcompare fileコマンドでdiffを取ろうとすると……謎のエラーが出ます。

set oldPath to choose file
set newPath to choose file

set oldPath to oldPath as string
set newPath to newPath as string

tell application “TextWrangler”
  compare file oldPath against file newPath
end tell

こんな、最低限の基礎的な記述に戻してあげて(トラブル時にものすごく大事なやりかた)、テキストに書き出したAppleScriptを2つ指定してみると……あいかわらずエラーになります。

 「もう、TextWranglerじゃなくて別のツールでも使おうか……」

日も暮れて、そう考えかけたころ、「fileで指定しているのがいけないのでは?」と気付き、aliasで渡してみたら何事もなかったように表示されました。

tw_diff106.jpg

そもそも、TextWranglerのAppleScript用語辞書(アプリケーションのアイコンをAppleScriptエディタにドラッグ&ドロップすると表示)を見てみると、

twdic.jpg

などと書いてあるので、「そうかーaliasじゃダメなんだー」と受け取ったからです。これは、TextWranglerのAppleScript用語辞書が間違っています。

asdic.jpg

▲こんな風に書かなくては(AppleScriptエディタの用語辞書より「open」命令の記述)

このScriptをアプリケーション形式で保存し、出来上がったドロップレットに2つのAppleScript書類をドロップすると、TextWranglerでdiff表示を行います。

スクリプト名:asdiff
on run
  –環境確認を行うべき(書いてない)
  
  
–FileMergeの起動を最初にやっておく
  
tell application “System Events”
    set fmExists to (exists of process “TextWrangler”)
  end tell
  
  
if fmExists = false then
    tell application “TextWrangler”
      launch
    end tell
  end if
  
end run

on open fileList
  
  
tell application “Finder”
    set sortedList to sort fileList by creation date
  end tell
  
  
set sortedList to reverse of sortedList
  
  
set oldPath to writeASSourceToTempFolder((item 1 of sortedList) as alias)
  
set newPath to writeASSourceToTempFolder((item 2 of sortedList) as alias)
  
  
–do shell script “/usr/bin/opendiff ” & oldPath’s POSIX path’s quoted form & ” ” & newPath’s POSIX path’s quoted form & ” > /dev/null 2>&1 &”
  
  
tell application “TextWrangler”
    compare oldPath against newPath –Mac OS X 10.6用にaliasで渡すように書き換えた
  end tell
  
  
end open

–AppleScriptのソースを取得してファイルに書き出し
on writeASSourceToTempFolder(aScript)
  –ASのソースを取得
  
set scriptSource to getContentsOfScript(aScript) of me
  
if scriptSource = false then
    display dialog “指定のASのオープン時にエラーが発生”
    
return false –エラー
  end if
  
  
tell application “Finder”
    set origName to name of file aScript
  end tell
  
set origName to makestr_alphabetNumeric_only(origName) of me
  
  
  
–tmpにASのソースを一時ファイルとして保存
  
set tmpPath to (path to temporary items from system domain) as string
  
set aFN to do shell script “/bin/date +%Y%m%d_%H%M%S”
  
set tmpPathFull to tmpPath & origName & “_” & aFN & “.txt”
  
set fRes to write_to_file_UTF8(scriptSource, tmpPathFull, false) of me
  
if fRes = true then
    return tmpPathFull as alias –書き出したテキストのフルパスを返す(10.6用に変更)
  else
    return false –エラー
  end if
end writeASSourceToTempFolder

–指定したAppleScriptのソースを取得する(Mac OS X 10.4用)
on getContentsOfScript(aScript)
  tell application “AppleScript Editor”
    try
      with timeout of 600 seconds –アプリケーションの起動に時間がかかるケースもあるので180秒から延長
        set aScript to open aScript
      end timeout
    on error
      return false
    end try
    
    
tell window 1
      set aName to name
    end tell
    
    
tell document aName
      set aCon to contents
      
close without saving
    end tell
  end tell
  
  
return aCon
end getContentsOfScript

–ファイルへのUTF8での書き込み
on write_to_file_UTF8(this_data, target_file, append_data)
  try
    set the target_file to the target_file as text
    
set the open_target_file to ?
      open for access file target_file with write permission
    if append_data is false then ?
      set eof of the open_target_file to 0
    write this_data to the open_target_file as «class utf8» starting at eof
    
close access the open_target_file
    
return true
  on error
    try
      close access file target_file
    end try
    
return false
  end try
end write_to_file_UTF8

–アルファベットと数字のみの文字列にして返す
on makestr_alphabetNumeric_only(aKeyword)
  set aKeyword to aKeyword as string
  
set aKeyword to aKeyword as Unicode text
  
  
set cList to characters of aKeyword
  
set newName to “”
  
  
repeat with i in cList
    set aRes to checkAN(i) of me
    
if aRes = true then
      set newName to newName & (i as string)
    end if
  end repeat
  
  
return newName
end makestr_alphabetNumeric_only

on checkAN(aKeyword)
  set anList to {“a”, “b”, “c”, “d”, “e”, “f”, “g”, “h”, “i”, “j”, “k”, “l”, “m”, “n”, “o”, “p”, “q”, “r”, “s”, “t”, “u”, “v”, “w”, “x”, “y”, “z”, “-”, “+”, “.”, “_”, “=”, “(”, “)”, “#”, “$”, “%”, “&”, “~”, “^”, “0″, “1″, “2″, “3″, “4″, “5″, “6″, “7″, “8″, “9″}
  
  
set aKeyword to aKeyword as Unicode text
  
set aKeyword to aKeyword as string
  
set kList to characters of aKeyword
  
repeat with i in kList
    ignoring case
      if i is not in anList then
        return false
      end if
    end ignoring
  end repeat
  
return true
end checkAN

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

01/09 指定Finder Windowのツールバー&サイドバーの表示、非表示をコントロール

指定Finder Windowのツールバーおよびサイドバーの表示/非表示状態を制御するAppleScriptです。

FinderのAppleScript用語辞書をオープンすると、サイドバーを表す要素が見つかりません。そこで、サイドバーの表示/非表示の制御はできないもの……と、思ってしまわれがちですが、サイドバーはウィンドウ上部のツールバーと表示/非表示制御が一括して行われているため、ツールバーの制御を行うと、一緒に表示状態をコントロールできます。

finwin1.jpg

finwin2.jpg

裏を返せば、サイドバー/ツールバーともに単独で状態を制御できないということになりますが、それはOSの(ウィンドウシステムの)仕様なので、言っても仕方のないところ。

AppleScriptで各種アプリケーションをコントロールするには、こうしたOSやアプリケーションの挙動に対する「暗黙のお約束」的な仕様を意識する必要があり、その典型的な例として挙げてみました(このサブルーチン自体には…………あまり利用価値がないような、、、サブルーチン化しなくても、もっと簡潔に書けるわけで)。

表示中のFinderのすべてのWindowに対して制御を行う場合には、tell every window……と書いたほうが効率的で、すべてのFinder Windowをリストに入れてループ処理……というのはAppleScript「らしい」やりかたではありません。ねんのため。

スクリプト名:指定Finder Windowのツールバー&サイドバーの表示、非表示をコントロール
tell application “Finder”
  set targWin to window 1 –最前面のWindowを指定。任意のWindow指定も可能
end tell

showHideToolbarAndSidebar(targWin, false) of me –非表示にする
–showHideToolbarAndSidebar(targWin, true) of me –表示にする

–指定Finder Windowのツールバー&サイドバーの表示、非表示をコントロール
on showHideToolbarAndSidebar(targetWindow, showF)
  try
    tell application “Finder”
      tell targetWindow
        set toolbar visible to showF
      end tell
    end tell
  end try
end showHideToolbarAndSidebar

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

01/05 文字入力モードを制御

GUI Scripting経由でSystemUIServerを制御して、IMの文字入力モードを変更するAppleScriptです。

実行には、GUI Scriptingがオンになっている必要があります。また、現時点ではMac OS X 10.6.8と10.7.2で確認してある状態です(10.5は微妙。10.4ではダメだと思います)。

→ 後日確認したところ、Mac OS X 10.5.8と10.4.11では動きませんでした。予想どおり。

文字入力モードをAppleScriptからコントロールするのは、普通に考えれば無理そうです。

真っ先に思いつくのが、AppleScriptObjCによるGUIつきアプリケーションを作成して、そのアプリケーションのWindow上にNSTextFieldを作成し、入力文字種類を制御するやりかたです。これなら、文字入力を制御できているといえなくもありません。

サードパーティのIMまで目を向けると、ジャストシステムのATOKはATOKダイレクトAPIなるAPIをユーザーに公開しており、コントロールできなくもなさそうな雰囲気はしているのですが、メーカーが想定している使い方しかさせてもらえなさそうな雰囲気も漂っています。

AppleScriptObjC経由で、ことえりのステータスを変更ないしは固定するようなプログラムを呼び出す方法も考えられなくもないですが……すぐには情報が見つかりませんでした。

そこで、きわめてAppleScript的に、GUI Scripting経由でコントロールしてみようということになりました。

画面上部のメニュー右側に表示されるMenu Extraは、SystemUIServerというプログラムが管轄しています(Mac OS X 10.6/10.7)。

menu1.jpg

このため、SystemUIServerのメニューの各アイテムにAppleScriptからアクセスすると、最低限、どのような内容を表示しているか取得できそうです。ユーザーによってMenu Extraの内容や並び順はカスタマイズされまくっているので、Menu Extra同士の識別ができなくてはなりません。

descriptionという属性を調べることで、どのプログラムが表示しているものか識別できそうです。Apple純正のMenu Extraしか値を取得できていない状態ですが、今回の目的のためにはこれで十分です。

並び順からいって、「text input」というのがIMのMenu Extraを表しているようです。

スクリプト名:System UI Serverから何がmenu extraを表示しているかを取得
activate application “SystemUIServer”
tell application “System Events”
  tell process “SystemUIServer”
    tell menu bar 1
      set aList to description of every menu bar item
    end tell
  end tell
end tell
–> {”time machine”, “bluetooth”, “iChat”, “displays”, “AirMac Menu Extra”, “システムサウンド音量”, “AppleScript”, “バッテリーメニュー。 完全充電まで 5 時間 39 分 .”, “clock”, “text input”, “user”}

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

次に、どのプログラムがどのような情報を表示しているのか「value」属性を調べてみました。「英字」「ひらがな」「カタカナ」といった情報が取得できます。けっこういい感じです。

スクリプト名:System UI Serverから各menu extraが何を表示しているかを取得
activate application “SystemUIServer”
tell application “System Events”
  tell process “SystemUIServer”
    tell menu bar 1
      set aList to value of every menu bar item
    end tell
  end tell
end tell
–> {missing value, missing value, missing value, missing value, “Extreme net の 4 本のうち 4 本の信号”, missing value, missing value, missing value, “1月4日(水) 23:49″, “英字”, missing value}

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

ちなみに、このvalueをAppleScriptから変更できないか試してみたのですが、valueはあくまで現状を反映させたものであり、書き換えてみても何も起きませんでした。

そこで、これまたGUI Scripting的なアプローチで、この「text input」のmenu extraをクリックして、表示されたメニューから指定のアイテムをクリックするという操作を行ってみました。

スクリプト名:System UI Serverを操作してIMの文字入力状態を変更する

setInputState(“英字”) of me
–setInputState(”ひらがな”) of me

–IMの入力状態を設定する
on setInputState(aState)
  activate application “SystemUIServer”
  
tell application “System Events”
    tell process “SystemUIServer”
      tell menu bar 1
        set aList to every menu bar item whose description is “text input”
        
set anItem to first item of aList
        
set curVal to value of anItem
        
        
if aState = curVal then return –変更の必要がなければリターン
        
        
tell anItem
          click
          
tell menu 1
            set mList to every menu item whose title is aState
            
set m1Item to first item of mList
            
tell m1Item
              click
            end tell
          end tell
        end tell
        
      end tell
    end tell
  end tell
  
end setInputState

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

人間、やればできるもんです。できないかと思っていたのですが、やってみたら案外手軽にできてしまいました。

ただし、複数のIMがインストールされている環境では、それぞれを識別するのはちょっと難しそうで……

menu2.jpg

もうちょっと調べてみないとなんともいえないところでしょうか。

01/03 作成日時からラジオ録音番組名を設定

録音したRadikoの音声ファイルを、それぞれの番組の条件リストをもとにリネームするAppleScriptです。

r0013976.JPG

自作のRadiko録音アプリ「Radirec」で録音しためたファイルに対し、条件リストをもとにファイル作成日時をてがかりに番組判定を行います。

条件リストはこんな感じに……番組名、放送曜日、開始時刻、終了時刻のセットです。

property progList : {{“日曜天国”, Sunday, “10:00″, “11:55″}, {“深夜の馬鹿力”, Tuesday, “01:00″, “03:00″}, {“ジブリ汗まみれ”, Sunday, “23:00″, “23:30″}}

rad1.jpg

もともと、録音アプリケーション(Radirec)のほうでこうした処理をしておけばよかったのですが、最低限の機能だけで安定して動作すればいいやぐらいで作ったので……あとで苦労しているわけで。

年末年始は寝て過ごしていたので、ちょっとプログラミングのカンが鈍ってしまい、ちょっとしたリハビリがわりにつくってみたというところでしょうか。

a reference toによるアクセス高速化ではなく、別Scriptのpropertyに対してアクセスするタイプの高速化を行っています。あんまり効き目のなさそうなところにも使っていますが、AppleScriptObjCでの高速化のための練習、みたいな感じでしょうか。

これまでのサブルーチンはAppleScript Studioに入れられることを前提に作ってきましたが、これからはAppleScriptObjCを前提に作る必要があります。

rad2.jpg

録り貯めたファイルのうち、ほとんどは処理できたものの、一部ファイルの判定は行えなかったので、まだ納得できていないレベルです。

スクリプト名:作成日時からラジオ録音番組名を設定
–スピードアップ記述の練習用に(意味もないのに)プロパティを別Scriptオブジェクトに分けてみた
script speedUp
  –ファイル一覧のハンドリング用
  
property fList : {}
  
  
–区分けする番組リスト(番組名、放送曜日(開始時刻のみ判定)、開始時刻、終了時刻
  
property progList : {{“日曜天国”, Sunday, “10:00″, “11:55″}, {“深夜の馬鹿力”, Tuesday, “01:00″, “03:00″}, {“ジブリ汗まみれ”, Sunday, “23:00″, “23:30″}}
  
  
–処理対象ファイルの拡張子リスト
  
property extList : {“mov”, “mp3″, “m4a”}
  
end script

–録音日時が日付をまたがないことが処理条件
–もうちょっと機能アップしないと不十分?

set falseList to {} –処理できなかったファイルの一覧が入る

set tFol to choose folder with prompt “処理対象フォルダを指定してください”

tell application “Finder”
  –指定フォルダ内のファイル一覧を取得(指定フォルダ以下のすべてのフォルダ内を走査)
  
try
    set (fList of speedUp) to entire contents of tFol as alias list
  on error
    display dialog “Error” buttons {“OK”} default button 1 with icon 2
    
return
  end try
end tell

–メインループ

repeat with i in (fList of speedUp)
  –set j to contents of i
  
set aInfo to info for i
  
  
–ファイル拡張子で処理対象を判別
  
tell application “Finder”
    set aExt to name extension of aInfo
    
–log aExt
    
if aExt is in (extList of speedUp) then
      
      
–作成日時と作成曜日を取り出す
      
set sDate to creation date of aInfo
      
–log sDate
      
      
set yNum to year of sDate
      
set dayNum to weekday of sDate as number
      
set monthNum to month of sDate as number
      
set dateNum to day of sDate
      
      
–番組リストとの照合
      
set hitF to false
      
repeat with ii in progList of speedUp
        set {pName, sWeekday, sTime, eTime} to ii
        
        
if (sWeekday as number) = dayNum then
          –開始時刻のdate objectを求める
          
set s1Time to sDate
          
set {h1Num, m1Num} to words of sTime
          
set h1Num to h1Num as number
          
set m1Num to m1Num as number
          
set hours of s1Time to h1Num
          
set minutes of s1Time to m1Num
          
set s1Time to s1Time - (10 * 60) –開始時刻を10分前倒しで判定
          
          
–終了時刻のdate objectを求める
          
set e1Time to sDate
          
set {h2Num, m2Num} to words of eTime
          
set h2Num to h2Num as number
          
set m2Num to m2Num as number
          
set hours of e1Time to h2Num
          
set minutes of e1Time to m2Num
          
set e1Time to e1Time + (10 * 60) –終了時刻を10分後ろ倒しで判定
          
          
          
if s1Time sDate and sDate e1Time then
            –ファイル名指定用の数値データを桁数指定しつつ文字列化
            
set yStr to retZeroPaddingText(yNum, 4) of me
            
set mStr to retZeroPaddingText(monthNum, 2) of me
            
set dStr to retZeroPaddingText(dateNum, 2) of me
            
set targStr to pName & ” “ & yStr & ” “ & mStr & “月” & dStr & “日.” & aExt –ここを変更するとファイル名のフォーマットが変わる
            
            
set name of i to targStr –ファイルの名称変更
            
            
set hitF to true
            
            
exit repeat
            
          end if
        end if
      end repeat
      
      
if hitF = false then
        set the end of falseList to (contents of i)
      end if
      
    else
      set the end of falseList to (contents of i)
    end if
  end tell
end repeat

return falseList

–数値にゼロパディングしたテキストを返す
on retZeroPaddingText(aNum, aLen)
  set tText to (“0000000000″ & aNum as text)
  
set tCount to length of tText
  
set resText to text (tCount - aLen + 1) thru tCount of tText
  
return resText
end retZeroPaddingText

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/29 ファイル作成曜日に応じてラベルを付ける

指定フォルダ内の全ファイルのファイル作成日を調べ、指定曜日に作成されたものにラベルを振るAppleScriptです。

Radikoのタイマー録音を行う自作のAppleScriptObjCアプリケーションで、いろんな番組を録り貯めていたのですが……録り貯めた膨大なファイルの中から特定の番組だけを抽出するために、作成したものです。

label0.jpg

lebel.jpg

▲伊集院光「深夜の馬鹿力」(月曜日25:00〜27:00……つまり、「火曜日」)を抽出したところ。

label2.jpg

スクリプト名:ファイル作成曜日に応じてラベルを付ける
set aFol to choose folder with prompt “処理対象のファイルが入っているフォルダを選択”

set aList to {“日曜日”, “月曜日”, “火曜日”, “水曜日”, “木曜日”, “金曜日”, “土曜日”}
set aMes to “ラベルを付ける対象曜日を選択してください。”
set targDay to retNumberFromChooseFromList(aList, aMes) of me

tell application “Finder”
  tell folder aFol
    set aList to every file as alias list
  end tell
end tell

repeat with i in aList
  set aInfo to info for i
  
set sDate to creation date of aInfo
  
set dayNum to weekday of sDate as number
  
  
if dayNum = targDay then
    tell application “Finder”
      set label index of i to 2 –赤いラベル
    end tell
  end if
end repeat

–リストからの選択ダイアログで、選択したアイテムのアイテムナンバーを返す
on retNumberFromChooseFromList(aList, aMes)
  set aRes to choose from list aList with prompt aMes
  
if aRes = false then return false
  
set aRes to (contents of aRes) as string
  
set itemCount to 1
  
repeat with i in aList
    set j to contents of i
    
if j is equal to aRes then
      return itemCount
    end if
    
set itemCount to itemCount + 1
  end repeat
end retNumberFromChooseFromList

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/27 ファイル選択ダイアログを表示して、パスを取得する(Perl中へのAppleScript埋め込み)

いつもは、AppleScriptからshellコマンドを呼び出したり、AppleScript側からの操作を行っていますが、Perlのプログラムの中にAppleScriptを埋め込んでみました。Perlではありますが、AppleScriptの文字列を埋め込んで呼び出しているため、Mac OS Xの上でしか動きません。

たまたま(久しぶりに)Perlをさわる機会があって、渡されたプログラムを見たらファイルの指定方法がめんどうだったので、中にAppleScriptを埋め込んでファイル選択ダイアログを出してファイル選択できるようにしてみました。

AppleScriptのアプレット内でファイル選択なりドラッグ&ドロップを行って、Perlのプログラムに対して引数で渡すというやりかたもあると思います。

他の言語のプログラム(Objective-Cとか)の中にAppleScriptを埋め込んで処理を行わせているものも見られるのですが、「もうちょっとなんとかしたほうが」という内容のものが多いので、自分で納得できる機能を実装してみました。

結局、AppleScriptのchoose file経由で選択したパス情報を、いったんテキストファイルに書き出して、Perlのプログラム側からテキストファイルの内容を取得する、という仕組みになっています。ファイル経由でなくても値の受け渡しができるとベストです。

#ファイル選択ダイアログを表示して、パスを取得する
getFilePath();
$ret = `cat ~/Desktop/.retval.txt`;
print $ret ;

sub getFilePath {
  system(’osascript’, ‘-e’,
  ’tell application “Finder”
  activate
  set aFile to choose file with prompt “ファイルを選択してください”
  set aPOS to POSIX path of aFile
  end tell
  do shell script “echo ” & aPOS & ” > ~/Desktop/.retval.txt”
  ’);
}

12/24 AppleScriptObjCでの高速化手法〜script文によるpropertyアクセス

AppleScriptObjCでさまざまな小物ツールを作っては日常的に使っています。

ただ、困ったことに……大量のリスト型変数を扱うのに必須な、「a reference to」によるリスト型変数の間接アクセスによる高速化が、AppleScriptObjCの環境では使えません(プログラム中に記述できるものの、実行時にエラーになってしまいます)。

そこで、いろいろ高速化の手段はないものかと考えることに。

実際に、カラーピッカーで選んだ任意の色の類似色を1,000色ぐらいの色見本データからピックアップして提示する、というツールを作ってみたときに、a reference toによるアクセスができず、AppleScriptエディタ上で動作させるよりも遅くなってしまいました。これは由々しき事態です。

そこで、同じ問題に直面している人がいるのではないかと考え、海外のサイトを探し回り……MacScripterでそのものズバリの内容を見つけました。

「Objective-Cに書き換えれば高速化が……」という身もふたもない内容の投稿もありましたが、a reference toと同程度の高速化が実現できる手法を見つけ、実際に自分のコードに入れてみて効果を確認。

それが、script文によって(論理的に)別Scriptにpropertyを追い出して、別Script上のpropertyにアクセスするというものです。

script speedUp
  property aList:{}
end script

script mainScriptDelegate

  set the end of speedUp’s aList to 1
  
end script

のように間接アクセス。これで10倍以上の(こまかく計測していないのですが)スピードアップが図れました。たかだか1,000項目ぐらいではテストデータとしては小さすぎるので、数万アイテムぐらいのテストデータを作って、別途試してみたいところです。

このほか、ソートルーチンにいつものshell sortではなくCocoaベースのソートルーチンを投入してみました。ちょっとこちらの効果は定かではないのですが、前述の近似色サーチのAppleScriptObjCプログラムでは、

 スピードアップいっさいなし:8〜10秒ぐらい
 +Cocoaベースのソートを投入:3〜4秒ぐらい
 +Script文による間接アクセス適用:1秒以下

ぐらいの差が出たので、Cocoaベースのソートルーチンもそれなりに効いているのでしょう。このCocoaベースのソートルーチンは、{{age:20, aName:”ひよこさん”}, {age:43, aName:”ぴよぴよ”}}のような構造のリストをソートするものです。入れ子のリスト……というと厳密には正しくはないですね。レコードのリストというべきか。KeyItemには”age”とか”aName”といったラベルを指定します。

スクリプト名:Cocoaで入れ子のリストを昇順ソート
–Cocoaで入れ子のリストを昇順ソート(AppleScriptObjC)
on cocoaSortListAscending(theList, keyItem)
  
  
– make unique set
  
tell current application’s NSSet to set theSet to setWithArray_(theList)
  
  
– define sorting
  
tell current application’s NSSortDescriptor
    set theDescriptor to sortDescriptorWithKey_ascending_(keyItem, true)
  end tell
  
  
–sort
  
set sortedList to theSet’s sortedArrayUsingDescriptors_({theDescriptor})
  
  
return sortedList
  
end cocoaSortListAscending

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/23 Google Maps API Web ServicesをAppleScriptから呼び出す「Location Helper」

GoogleのMaps API Web ServicesにAppleScriptから簡単にアクセスできるようにするツール「Location Helper for AppleScript」がMacApp Storeからフリーで配布されています

作者は、David Blishen氏

jhelp.jpg

同氏はほかに「JSON Helper」という同様のツールもMac AppStoreで同様にフリー配布しており、こちらも注目していました(Location HelperはJSON Helperの機能を含んでいます)。

JSON Helper/Location Helperはともにバックグラウンドで稼働するGUIなしアプリケーションであり、起動してもDockには起動表示は出ません(Mac AppStoreの評価コメントに「起動しない」といったものがありますが、完全な勘違いです)。アクティビティモニタで見ると、Location Helperが起動していることが確認できます。

それでは、Location HelperのGoogle Maps API関連の機能を実際にAppleScriptから呼び出してみましょう。

実行時には、インターネットへの接続が必要となります。

スクリプト名:現在地の緯度、経度を求める
tell application “Location Helper”
  get location coordinates
end tell
–> {35.xxxxxxxxxxxxx, 139.xxxxxxxxxxxx}

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

現在位置については、初回起動時に勝手に取得してよいかダイアログが表示されます。

loc1.jpeg

スクリプト名:現在地の海抜を求める
tell application “Location Helper”
  get location altitude
end tell
–> -1

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

-1が返ってくる場合には海抜の情報が取得できていないケース、とAppleScript用語辞書に書かれています。

スクリプト名:現在地の情報をレコード形式で取得
tell application “Location Helper”
  get location record
end tell
–> {course:-1.0, v_accuracy:-1.0, speed:0.0, timestamp:date “2011年12月23日金曜日 16:30:08″, coordinates:{35.xxxxxxxxxxxxx, 139.xxxxxxxxxxxxx}, lat:35.xxxxxx, lng:139.xxxxxxx, h_accuracy:127.0, altitude:-1}

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

スクリプト名:現在地の情報をテキスト形式で取得
tell application “Location Helper”
  get location string
end tell
–> “< +35.xxxx, +139.xxxx> +/- 127.00m (speed 0.00 mps / course -1.00) @ 2011-12-23 16:30:08 +0900″

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

スクリプト名:現在地の緯度・経度から住所情報を取得
tell application “Location Helper”
  reverse geocode location
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

スクリプト名:指定住所の緯度・経度情報を取得
tell application “Location Helper”
  geocode address “東京都新宿区西新宿三丁目20-2″ –アップルジャパン合同会社の所在地
end tell
–> {results:{{formatted_address:”日本, 東京都新宿区西新宿3丁目20−2”, address_components:{{short_name:”2”, long_name:”2”, types:{”sublocality_level_4″, “sublocality”, “political”}}, {short_name:”20”, long_name:”20”, types:{”sublocality_level_3″, “sublocality”, “political”}}, {short_name:”3丁目”, long_name:”3丁目”, types:{”sublocality_level_2″, “sublocality”, “political”}}, {short_name:”西新宿”, long_name:”西新宿”, types:{”sublocality_level_1″, “sublocality”, “political”}}, {short_name:”新宿区”, long_name:”新宿区”, types:{”locality”, “political”}}, {short_name:”東京都”, long_name:”東京都”, types:{”administrative_area_level_1″, “political”}}, {short_name:”JP”, long_name:”日本”, types:{”country”, “political”}}}, geometry:{viewport:{northeast:{lat:35.684300980292, lng:139.688203280291}, southwest:{lat:35.681603019708, lng:139.685505319709}}, location:{lat:35.682952, lng:139.6868543}, location_type:”APPROXIMATE”}, types:{”sublocality_level_4″, “sublocality”, “political”}}}, status:”OK”}

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

スクリプト名:現在地と指定の緯度・経度との距離をメートルで返す
tell application “Location Helper”
  get distance from coordinates {35.739, 139.63}
end tell
–> 911.86243144354

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

JSON Helper / Location Helperは非常に有用なツールですが、AppleScriptでGUIつきアプリケーションを作る環境AppleScriptObjCで、プログラムと一緒にLocation Helperを配布するわけにもいかないので、同等の機能を自前で実装する必要が出てくる感じでしょうか。Location Helperとまで行かないまでも、JSON Helperの機能については、別途実装したいところです。

12/21 miで選択中の内容をファイルに書き出してperlのプログラムとしてterminalで実行 v2

miの最前面のドキュメント上で選択中のテキストをファイルに書き出して、perlのプログラムとしてTerminalで実行するAppleScriptのバグ修正版です。

最初のバージョンでは、改行コードがCRの状態で書き出されていたため、改行コードをLFに置換してから実行するようにしました。

初版では、選択範囲にコメント行が含まれているとエラーになっていたりしましたが、このバージョンではそういうことはありません。

スクリプト名:miで選択中の内容をファイルに書き出してperlのプログラムとしてterminalで実行 v2
tell application “mi”
  tell front document
    set this_data to selection
    
    
–エラー対策
    
if this_data = “” then
      display dialog “文字列が何も選択されていません” buttons {“OK”} default button 1 with icon 1 with title “エラー”
      
return
    end if
    
    
set this_data to this_data as Unicode text
    
  end tell
end tell

–改行コードがCRになっている部分をLFに置換(v2で追加)
set this_data to repChar(this_data, ASCII character 13, ASCII character 10) of me

set tmpPath to path to temporary items from user domain
set fStr to (do shell script “date +%Y%m%d%H%M%S”) & “.pl”
set fPath to (tmpPath as string) & fStr

write_to_fileUTF8(this_data, fPath, false) of me
do shell script “sync”

set sText to “perl -w “ & quoted form of POSIX path of fPath
doComInTerminalWindow(sText) of me

on doComInTerminalWindow(aCMD)
  tell application “Terminal”
    set wCount to count (every window whose visible is true)
    
    
– By wayne melrose
    
– Re: New window in Terminal.app
    
    
if wCount = 0 then
      –ウィンドウが1枚も表示されていない場合
      
do script aCMD
    else
      –すでにウィンドウが表示されている場合
      
do script aCMD in front window
    end if
  end tell
end doComInTerminalWindow

–ファイルの追記ルーチン「write_to_file」
–追記データ、追記対象ファイル、boolean(trueで追記)
on write_to_fileUTF8(this_data, target_file, append_data)
  try
    set the target_file to the target_file as text
    
set the open_target_file to open for access file target_file with write permission
    
if append_data is false then set eof of the open_target_file to 0
    
write this_data to the open_target_file as «class utf8» starting at eof
    
close access the open_target_file
    
return true
  on error error_message
    try
      close access file target_file
    end try
    
return error_message
  end try
end write_to_fileUTF8

–文字置換ルーチン
on repChar(origText, targStr, repStr)
  set {txdl, AppleScript’s text item delimiters} to {AppleScript’s text item delimiters, targStr}
  
set temp to text items of origText
  
set AppleScript’s text item delimiters to repStr
  
set res to temp as text
  
set AppleScript’s text item delimiters to txdl
  
return res
end repChar

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/20 与えられたテキストをPhotoshopでRGBデータと評価してCMYK値を返す

「XX, XX, XX」の形式のテキストをparseして、RGBデータとして評価し、Photoshop CS3でRGB→CMYKの変換を行うAppleScriptです。

AppleScriptObjCでカラーデータを入力するプログラムを作っていて、CMYK→RGBの変換は割とすぐできたのですが、RGB→CMYKで手こずり……PhotoshopならAppleScript3行でできてしまう処理。そこで、データをメール経由で収集したあとに手元のプログラムで(Photoshopの機能を用いて)変換することに。

非常に簡単で楽勝でした。

スクリプト名:与えられたテキストをPhotoshopでRGBデータと評価してCMYK値を返す
set aData to "64, 128, 0"
set {cNum, mNum, yNum, kNum} to retCMYKfromRGBstr(aData)
–> {87, 36, 100, 2}

–与えられたテキストをRGBデータと評価してCMYK値を返す
on retCMYKfromRGBstr(aData)
  if aData = "" then return {false, false, false, false}
  
  
set {rDat, gDat, bDat} to divideABC(aData, ",") of me
  
  
  
tell application "Adobe Photoshop CS3"
    set myCMYKColor to convert color {class:RGB color, red:rDat, green:gDat, blue:bDat} to CMYK
    
    
set cyanNum to cyan of myCMYKColor
    
set magentaNum to magenta of myCMYKColor
    
set yellowNum to yellow of myCMYKColor
    
set kuroNum to black of myCMYKColor
  end tell
  
  
set cyanNum to round cyanNum rounding as taught in school –四捨五入
  
set magentaNum to round magentaNum rounding as taught in school –四捨五入
  
set yellowNum to round yellowNum rounding as taught in school –四捨五入
  
set kuroNum to round kuroNum rounding as taught in school –四捨五入
  
  
return {cyanNum, magentaNum, yellowNum, kuroNum}
  
end retCMYKfromRGBstr

–文字列をparseする(R,G,B)の文字データを処理するためのもの
on divideABC(aStr, aDelim)
  set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to aDelim
  
set tList to every text item of aStr
  
set AppleScript’s text item delimiters to curDelim
  
  
copy tList to {item1, item2, item3}
  
try
    set item1 to item1 as integer
    
set item2 to item2 as integer
    
set item3 to item3 as integer
  on error
    return false
  end try
  
  
return {item1, item2, item3}
end divideABC

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

これを、Photoshop CS3からPhotoshop Elements 6に変更してみたら……きちんと動作するのですが、計算結果がPhotoshop CS3とは異なりました。

スクリプト名:与えられたテキストをPhotoshop ElementsでRGBデータと評価してCMYK値を返す
set aData to "64, 128, 0"
set {cNum, mNum, yNum, kNum} to retCMYKfromRGBstr(aData)
–> {77, 27, 100, 14}

–与えられたテキストをRGBデータと評価してCMYK値を返す
on retCMYKfromRGBstr(aData)
  if aData = "" then return {false, false, false, false}
  
  
set {rDat, gDat, bDat} to divideABC(aData, ",") of me
  
  
  
tell application id "com.adobe.PhotoshopElements" –Phothop Elements
    set myCMYKColor to convert color {class:RGB color, red:rDat, green:gDat, blue:bDat} to CMYK
    
    
set cyanNum to cyan of myCMYKColor
    
set magentaNum to magenta of myCMYKColor
    
set yellowNum to yellow of myCMYKColor
    
set kuroNum to black of myCMYKColor
  end tell
  
  
set cyanNum to round cyanNum rounding as taught in school –四捨五入
  
set magentaNum to round magentaNum rounding as taught in school –四捨五入
  
set yellowNum to round yellowNum rounding as taught in school –四捨五入
  
set kuroNum to round kuroNum rounding as taught in school –四捨五入
  
  
return {cyanNum, magentaNum, yellowNum, kuroNum}
  
end retCMYKfromRGBstr

–文字列をparseする(R,G,B)の文字データを処理するためのもの
on divideABC(aStr, aDelim)
  set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to aDelim
  
set tList to every text item of aStr
  
set AppleScript’s text item delimiters to curDelim
  
  
copy tList to {item1, item2, item3}
  
try
    set item1 to item1 as integer
    
set item2 to item2 as integer
    
set item3 to item3 as integer
  on error
    return false
  end try
  
  
return {item1, item2, item3}
end divideABC

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/18 miで選択中の内容をファイルに書き出してperlのプログラムとしてterminalで実行

miでオープン中の最前面のドキュメントで選択中のテキストを書き出して、Terminalでperlのプログラムとして実行するAppleScriptです。

コメント行を含む内容を実行するとエラーになるのはなぜなんでしょう?(Perl詳しくないもんで)

mi21.jpg

mi11.jpg

スクリプト名:miで選択中の内容をファイルに書き出してperlのプログラムとしてterminalで実行
tell application “mi”
  tell front document
    set this_data to selection
    
    
–エラー対策
    
if this_data = “” then
      display dialog “文字列が何も選択されていません” buttons {“OK”} default button 1 with icon 1 with title “エラー”
      
return
    end if
    
    
set this_data to this_data as Unicode text
    
  end tell
end tell

–set this_data to “print ‘test’;”

set tmpPath to path to temporary items from user domain
set fStr to (do shell script “date +%Y%m%d%H%M%S”) & “.pl”
set fPath to (tmpPath as string) & fStr

write_to_fileUTF8(this_data, fPath, false) of me
do shell script “sync”

set sText to “perl -w “ & quoted form of POSIX path of fPath
doComInTerminalWindow(sText) of me

on doComInTerminalWindow(aCMD)
  tell application “Terminal”
    set wCount to count (every window whose visible is true)
    
    
– By wayne melrose
    
– Re: New window in Terminal.app
    
    
if wCount = 0 then
      –ウィンドウが1枚も表示されていない場合
      
do script aCMD
    else
      –すでにウィンドウが表示されている場合
      
do script aCMD in front window
    end if
  end tell
end doComInTerminalWindow

–ファイルの追記ルーチン「write_to_file」
–追記データ、追記対象ファイル、boolean(trueで追記)
on write_to_fileUTF8(this_data, target_file, append_data)
  try
    set the target_file to the target_file as text
    
set the open_target_file to open for access file target_file with write permission
    
if append_data is false then set eof of the open_target_file to 0
    
write this_data to the open_target_file as «class utf8» starting at eof
    
close access the open_target_file
    
return true
  on error error_message
    try
      close access file target_file
    end try
    
return error_message
  end try
end write_to_fileUTF8

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/12 選択中のボックスを表に再構成する

OmniGraffleで作成した表をいったん分解して、バラバラにした状態のものを再度「表」に再構成するAppleScriptです。

まずは、OmniGraffle上で表を作成するところから……

tbl1.jpg

完成時のサイズの見当をつけるために、ダミーのボックスをOmniGraffle上で作成し、選択状態にしておく。

tbl2.jpg

Numbers上に表に入れるデータを入力して、表に入れるデータの範囲を選択状態にしておく。

tbl3.jpg

NumbersのデータからOmniGraffle書類上に表を作成するAppleScriptを実行し、OmniGraffle上で表を作成。

tbl4.jpg

いったん作成した「表」を「グループ解除」コマンドで……

tbl5.jpg

再度、バラバラに。

この状態で、本AppleScriptを実行すると……

tbl6.jpg

表の縦、横のセル数をダイアログで聞いてくるので、それに対して適切に入力すると……

tbl7.jpg

ふたたび、表に戻すことができます。このサンプルがやっつけで作ったもののためか、ヘッダー行が入れ替わっていますが……実際に作業で再編集した表は、表の下に新規行を追加するパターンがほとんどでした。

スクリプト名:選択中のボックスを表に再構成する
–選択中のボックスを表に再構成する
tell application “OmniGraffle 5″
  tell front window
    set aSel to selection
    
    
–ここ、計算で誤差を無視して縦x横のオブジェクト数をカウントするとベスト
    
set aText to (text returned of (display dialog “縦x横のセル数を入力” default answer “2×2″))
    
set aRes to divideYxX(aText) of me
    
if aRes = false then
      display dialog “入力内容に誤りがありました。” buttons {“OK”} default button 1 with icon 1
      
return
    end if
    
    
try
      assemble aSel table shape aRes
    end try
    
  end tell
end tell

–「YxX」の形式の文字列を、{Y,X}のように数字のリストにして返す
on divideYxX(aStr)
  set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to “x”
  
set tList to every text item of aStr
  
set AppleScript’s text item delimiters to curDelim
  
  
copy tList to {item1, item2}
  
try
    set item1 to item1 as integer
    
set item2 to item2 as integer
  on error
    return false
  end try
  
  
return {item1, item2}
  
end divideYxX

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/12 「YxX」の形式の文字列を、{Y,X}のように数字のリストにして返す

「YxX」の形式の文字列を、{Y,X}のように数字のリストにして返すAppleScriptです。

ダイアログから縦横の表のサイズを文字で入力させるインタフェースを作成したときに、そのparse用として作成したものです。たいしたものではありません。

スクリプト名:「YxX」の形式の文字列を、{Y,X}のように数字のリストにして返す
set aStr to "3×4"
set aRes to divideYxX(aStr) of me
–> {3, 4}

–「YxX」の形式の文字列を、{Y,X}のように数字のリストにして返す
on divideYxX(aStr)
  set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to "x"
  
set tList to every text item of aStr
  
set AppleScript’s text item delimiters to curDelim
  
  
copy tList to {item1, item2}
  
try
    set item1 to item1 as integer
    
set item2 to item2 as integer
  on error
    return false
  end try
  
  
return {item1, item2}
  
end divideYxX

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/12 「A x B = C」の数の組み合わせを求める

与えられた数を「A x B」で構成する2つの数の組み合わせをすべて求めるAppleScriptです。

数学的な公式とか、特殊な計算などいっさいなしで、単にループで回しつつ除算を行ってあまりが発生しない数をリストアップしているだけです。

OmniGraffleで表を作成し、グループ化を解除して表の中をいろいろ編集して、再度「表」として構成するさいに、selectionから自動で構成セル数を計算できないか……などと調べている最中に作成したものです。

Finder上に並んでいるアイコンの座標および大きさから縦横のセル数を求めるAppleScriptなど、過去にこのようなものは作ったことがないわけではないのですが、(超高性能なAppleScriptのプログラムについては)いつも「勢い」で一気に作ってしまうので、案外使い回しが利きません。

スクリプト名:「A x B = C」の数の組み合わせを求める
set c to 24
set aResList to calcAxB(c) of me
–> {{24, 1}, {12, 2}, {8, 3}, {6, 4}, {4, 6}, {3, 8}, {2, 12}, {1, 24}}

–「A x B = C」の数の組み合わせを求める
on calcAxB(aMountNum)
  set aList to {}
  
  
repeat with i from 1 to aMountNum
    set aMod to aMountNum mod i
    
if aMod = 0 then
      set aDiv to aMountNum div i
      
set the end of aList to {aDiv, i}
    end if
  end repeat
  
  
return aList
  
end calcAxB

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/10 Skimで、現在表示中のページを別ファイルにPDF形式で書き出す

オープンソースのPDFビューワー「Skim」で、現在表示中のPDFのページを別のPDFに書き出すAppleScriptです。

最初は、現在表示中のページだけを残して、他のページを「削除する」AppleScriptを書いていたのですが、Skimではpageを削除できないことにあとから気付きました。

そこで、急遽「ページごとに別ファイルに書き出す」Scriptを機能ダウンさせて現在のページのみPDFで別ファイルに書き出すように変更。

スクリプト名:Skimで、現在表示中のページを別ファイルにPDF形式で書き出す
tell application “Skim”
  tell document 1
    set aBounds to selection bounds
    
tell current page
      set grabData to grab for aBounds as PDF
    end tell
  end tell
end tell

set newAlias to choose file name
write_to_file(grabData, newAlias, false) of me

–ファイルの追記ルーチン「write_to_file」
–追記データ、追記対象ファイル、boolean(trueで追記)
on write_to_file(this_data, target_file, append_data)
  try
    set the target_file to the target_file as text
    
set the open_target_file to open for access file target_file with write permission
    
if append_data is false then set eof of the open_target_file to 0
    
write this_data to the open_target_file starting at eof
    
close access the open_target_file
    
return true
  on error error_message
    try
      close access file target_file
    end try
    
return error_message
  end try
end write_to_file

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

こちらが、動かないほうのバージョンです。コンパイル(構文確認)も通るのですが、アプリケーション側がサポートしていないオブジェクトとコマンドの組み合わせだと動かないという、いい見本です。

スクリプト名:Skimで、現在表示中のページ以外をすべて削除する(ダメ)
–そもそも、pageをdeleteすることができなかった
–→ 現在のページだけをExportする方向で書き直し

tell application “Skim”
  tell document 1
    set curP to index of current page –現在表示中のページ番号(index)を取得
    
set totalP to count every page –全ページ数を取得
    
    
    
if totalP = 1 then
      display dialog “1ページしか存在しない書類は処理対象外です” buttons {“OK”} default button 1 with icon 1
      
return
    end if
    
    
if curP = 1 then
      –現在表示中のページが1ページ目だった場合の処理
      
delete (pages 2 thru -1)
      
    else if curP = totalP then
      –現在表示中のページが最終ページだった場合の処理
      
delete (pages 1 thru -2)
      
    else
      –その他 通常処理
      
delete (pages 1 thru (curP - 1))
      
delete (pages (curP + 1) thru -1)
      
    end if
    
  end tell
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/10 SafariでNHKのラジオ局を選局する

Safariで指定のNHKラジオ局をオープンするAppleScriptです。

NHKが行っているインターネットストリーミングラジオ放送「らじる★らじる」のURLをオープンし、ページ内のJavaScriptを呼び出してラジオ局の切り換えを行います。この手の処理の「お約束」で、サイト側の構成が変わった場合には修正を行う必要があります。

radiko1.jpg

自分で使うためだけに作ったRadikoの録音用アプリ(AppleScriptObjCで作成)に、NHKラジオの録音用機能を追加するために作成したものです。

radiko3.jpg

居間でテレビにつないだMac miniが、本アプリケーションを毎週実行してラジオを録音しています。QuickTime Playerの問題で、たま〜に録音を失敗するのがご愛嬌ですが…………。

radiko2.jpg

Safariをコントロールしてラジオ局の放送を聴取し、システムの設定を変更してQuickTime Player X経由で録音を行うものです。

スクリプト名:SafariでNHKのラジオ局を選局する

–set rRes to openNHKstations(”r1″) of me –NHK第一
–set rRes to openNHKstations(”r2″) of me –NHK第二
set rRes to openNHKstations(“fm”) of me –NHK-FM

–NHKのラジオ局を選局する
on openNHKstations(aStation)
  try
    tell application “Safari”
      close every document
      
set nDoc to make new document with properties {URL:“http://www3.nhk.or.jp/netradio/”}
      
      
page_loaded(10) of me
      
      
do JavaScript “RRopen(’” & aStation & “‘);” in document 1
    end tell
    
    
return true
    
  on error
    return false
  end try
end openNHKstations

on page_loaded(timeout_value)
  delay 2
  
repeat with i from 1 to the timeout_value
    tell application “Safari”
      if (do JavaScript “document.readyState” in document 1) is “complete” then
        return true
      else if i is the timeout_value then
        return false
      else
        delay 1
      end if
    end tell
  end repeat
  
return false
end page_loaded

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/06 Numbersで選択中のデータをもとにOmniGraffleの選択中のグラフィックのサイズで表作成

Numbers上で選択中のデータをもとに、OmniGraffleで選択中のグラフィックのサイズの表を作成するAppleScriptです。

Numbersのテーブル上で表を作成する対象のデータを選択しておきます。OmniGraffleの最前面の書類上で、四角のボックスを作成して選択状態にしておきます。

この状態で本AppleScriptを実行すると、OmniGraffle上で四角のボックスを削除したうえで、起点やサイズなどを参考にしつつ表を作成します。

スクリプト名:Numbersで選択中のデータをもとにOmniGraffleの選択中のグラフィックのサイズで表作成
property cellYohakuPoint : 3 –セル内の余白(上下/左右を同値で指定)
property textPointSize : 12 –セル内の文字サイズ(ポイント数)
property textFontName : "HiraKakuProN-W3" –セル内のフォント
property headerFillCol : {0.901961, 0.901961, 0.901961} –ヘッダー行の塗り色(うすいグレー)

–Numbersの選択範囲からデータを取得する(Excel-Like Nested List Format)
set aList to retExcelLikeStyleDataFromNumbersSelection() of me

set heightOfList to length of aList –データの高さ(行数)を取得
set widthOfList to length of first item of aList –データの幅(列数)を取得

–Omni Graffleの選択オブジェクト情報を取得
tell application "OmniGraffle 5"
  tell front window
    set aSel to selection
    
set aaSel to first item of aSel
    
    
set {xSize, ySize} to size of aaSel
    
set {xOrigPos, yOrigPos} to origin of aaSel
    
    
delete aaSel –位置指定用のオブジェクトを削除
    
  end tell
end tell

–表のセルの大きさを計算する
set xStep to xSize / widthOfList
set yStep to ySize / heightOfList

–テーブル用のオブジェクトリスト
set tObjList to {}

tell application "OmniGraffle 5"
  tell canvas of front window
    
    
repeat with y from 1 to heightOfList
      repeat with x from 1 to widthOfList
        
        
set aText to contents of item x of item y of aList
        
        
–Numbersの表の空きセルからデータを取ったときに数値の0.0が返る現象への対策
        
if aText = 0.0 then
          set aText to ""
        end if
        
        
set tmpXpos to xOrigPos + ((x - 1) * xStep)
        
set tmpYpos to yOrigPos + ((y - 1) * yStep)
        
        
–セルを作成する
        
if y = 1 then
          –ヘッダー行の場合にはセル内を指定色で塗る
          
set aCellObj to make new shape at end of graphics with properties {fill:solid fill, fill color:headerFillCol, draws shadow:false, size:{xStep, yStep}, side padding:cellYohakuPoint, thickness:0.25, autosizing:vertically only, vertical padding:cellYohakuPoint, origin:{tmpXpos, tmpYpos}, text:{text:aText, font:textFontName, size:textPointSize}}
          
        else
          –通常行はセル内を塗りつぶさない
          
set aCellObj to make new shape at end of graphics with properties {fill:no fill, draws shadow:false, size:{xStep, yStep}, side padding:cellYohakuPoint, thickness:0.25, autosizing:vertically only, vertical padding:cellYohakuPoint, origin:{tmpXpos, tmpYpos}, text:{text:aText, font:textFontName, size:textPointSize}}
          
        end if
        
        
set the end of tObjList to aCellObj
        
      end repeat
    end repeat
    
    
–セルを表に合成する
    
assemble tObjList table shape {heightOfList, widthOfList}
    
  end tell
end tell

–Numbersで選択範囲を縦に区切ったリストを返す
on retExcelLikeStyleDataFromNumbersSelection()
  
  
tell application "Numbers"
    tell document 1
      tell sheet 1
        tell table 1
          set selList to value of every cell of selection range –選択範囲のデータを取得
          
          
set selName to name of selection range –選択範囲のrange情報を取得
          
set {s1, s2} to parseByDelim(selName, ":") of me
          
          
–始点の情報を取得する
          
set s1Row to (address of row of range s1) as integer
          
set s1Column to (address of column of range s1) as integer
          
          
–終点の情報を取得する
          
set s2Row to (address of row of range s2) as integer
          
set s2Column to (address of column of range s2) as integer
          
          
–選択範囲の情報を取得する
          
set selHeight to s2Row - s1Row + 1 –高さ(Height of selection range)
          
set selWidth to s2Column - s1Column + 1 –幅(Width of selection range)
          
        end tell
      end tell
    end tell
  end tell
  
  
set aLen to length of selList
  
set aaLen to selHeight
  
  
set bList to {}
  
repeat with i from 1 to aaLen
    set aHoriList to {}
    
    
repeat with ii from 1 to selWidth
      set j1 to ii + (i - 1) * selWidth
      
set tmpCon to contents of item j1 of selList
      
      
set aClass to class of tmpCon
      
if aClass = number or aClass = integer or aClass = real then
        set tmpCon to tmpCon as integer
      end if
      
      
set the end of aHoriList to tmpCon
    end repeat
    
    
set the end of bList to aHoriList
  end repeat
  
  
return bList
  
end retExcelLikeStyleDataFromNumbersSelection

on parseByDelim(aData, aDelim)
  set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to aDelim
  
set dList to text items of aData
  
set AppleScript’s text item delimiters to curDelim
  
return dList
end parseByDelim

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/06 最前面のドキュメントの全Canvas上のグラフィックがそれぞれグループ化されていないかチェック。されていなければグループ化

OmniGraffleで、最前面のドキュメントの全Canvas(ページ)上のグラフィックがそれぞれグループ化されているかを確認するAppleScriptです。

グループ化されていない場合には、強制的にグループ化します。

スクリプト名:最前面のドキュメントの全Canvas上のグラフィックがそれぞれグループ化されていないかチェック。されていなければグループ化
tell application "OmniGraffle 5"
  tell front document
    set cList to every canvas
    
set nList to name of every canvas
  end tell
  
  
–書き出し対象のドキュメントを特定する
  
set curDoc to document of front window
  
  
–ドキュメント内のすべてのCanvas(ページ)
  
repeat with i in nList
    set j to contents of i
    
    
set aRes to checkAGroupInCanvas(j, curDoc) of me
    
    
–グループ化され切っていないCanvasがあったらグループ化
    
if aRes = false then
      –表示を切り替えてグループ化      
      
set canvas of front window to canvas j of curDoc
      
      
tell canvas of front window
        assemble every graphic –これでグループ化
      end tell
    end if
    
  end repeat
  
end tell

–指定ドキュメントの指定Canvas上のグラフィックがグループ化されていないか(1つになっていないか)チェックして返す
on checkAGroupInCanvas(aCanvasName, curDoc)
  tell application "OmniGraffle 5"
    tell canvas aCanvasName of curDoc
      set gList to every graphic
      
      
set gLen to length of gList
      
      
if gLen > 1 then
        return false
      else if gLen = 1 then
        return true
      end if
      
    end tell
  end tell
end checkAGroupInCanvas

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/06 通常版のOmniGraffleでProfessional版でしか作れない「表」を作る

OmniGraffleで、Professional版でしか作れない「表」を作成するAppleScriptです。

AppleScript用語辞書を調べてみたら、OmniGraffleとOmniGraffle Professionalでは差がないことが判明。GUIがないだけで、中身は存在するようでした。

そこで、OmniGraffle Professionalの試用版を利用して表の作成AppleScriptを作成し、それをOmniGraffle用にtellブロックだけ書き換えてみたら……問題なく動きました。

作れますね、表。

スクリプト名:通常版のOmniGraffleでProfessional版でしか作れない「表」を作る
–通常版のOmniGraffleでProfessional版でしか作れない「表」を作る
tell application “OmniGraffle 5″
  tell canvas of front window
    make new shape at end of graphics with properties {fill:no fill, draws shadow:false, size:{197.499985, 18.0}, side padding:0, thickness:0.25, autosizing:vertically only, vertical padding:0, origin:{103.000008, 46.0}, text:{text:“ああああああ”, font:“HiraKakuProN-W3″}}
    
make new shape at end of graphics with properties {fill:no fill, draws shadow:false, size:{197.500015, 18.0}, side padding:0, thickness:0.25, autosizing:vertically only, vertical padding:0, origin:{300.5, 46.0}, text:{text:“いいいいい”, font:“HiraKakuProN-W3″}}
    
make new shape at end of graphics with properties {fill:no fill, draws shadow:false, size:{197.499985, 18.0}, side padding:0, thickness:0.25, autosizing:vertically only, vertical padding:0, origin:{103.000008, 64.0}, text:{text:“ うううううう”, font:“HiraKakuProN-W3″}}
    
make new shape at end of graphics with properties {fill:no fill, draws shadow:false, size:{197.500015, 36.0}, side padding:0, thickness:0.25, autosizing:vertically only, vertical padding:0, origin:{300.5, 64.0}, text:{text:“えええええええええええええええええええええええええええええ”, font:“HiraKakuProN-W3″}}
    
    
assemble (graphics -4 through -1) table shape {2, 2}
  end tell
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/06 現在表示中のCanvasに存在しているラインのうち青いものに影を付ける

OmniGraffleで、現在表示中のCanvasに存在しているラインのうち青いものだけに影を付けるAppleScriptです。

矢印だけに影を付けるとか、さまざまなフィルター参照が使えると便利なことでしょう。

スクリプト名:現在表示中のCanvasに存在しているラインのうち青いものに影を付ける
tell application "OmniGraffle 5"
  tell canvas of front window
    tell (every line whose stroke color is equal to {0.0, 0.0, 1.0}) –フィルタ参照で青いものだけ抽出
      set draws shadow to true
    end tell
  end tell
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/06 リンクつき目次を作成する v1

OmniGraffleで、リンクつき目次を作成するAppleScriptです。

目次を作るAppleScriptで、単なるテキストベースの目次を作成できました。

この、目次は単に「ページ数、タブ、タイトル、改行」の組み合わせで構成されているものですが、これを1行ごとに独立したオブジェクトに分解して、テキストを突っ込み、各ページへのリンクを埋め込みます。

OmniGraffle書類の状態では、あまりご利益はありませんが……OmniGraffleからPDFに書き出せば、リンクが有効な目次つきのPDFになるわけで、けっこう有用です。

スクリプト名:リンクつき目次を作成する v1
–選択中のテキストボックスの内容を行単位で分割して、それぞれを単独のテキストボックスに細分化して、
–「ページ番号 各ページのタイトル」と仮定してページ番号を取り出し、各ページにリンクを張る

tell application "OmniGraffle 5"
  
  
–選択中のテキストボックスの情報を取得
  
tell front window
    –選択中のオブジェクトを取得
    
set aSel to selection
    
if aSel = {} then return
    
set aaSel to first item of aSel
    
    
–選択中のオブジェクトの各種情報を取得する
    
set aText to text of aaSel
    
set {xSize, ySize} to size of aaSel –結果はPointで返る
    
set {xPos, yPos} to origin of aaSel –結果はPointで返る
    
    
set aFontName to font of text of aaSel
    
set aFontSize to size of text of aaSel
    
    
set aText to text of aaSel
    
    
delete aaSel –選択しておいたテキストボックスを削除する
    
  end tell
  
  
–Canvasの大きさを取得(目下、とくに意味なし)  
  
tell canvas of front window
    set {xCanvasSize, yCanvasSize} to page size –結果はPointで返る
    
–> {1155.0, 783.0}
  end tell
  
  
  
set aList to paragraphs of aText
  
set paraCount to length of aList
  
  
  
–書類および各Canvasの情報を取得する
  
set targDoc to document of front window
  
set canvasList to every canvas of targDoc
  
  
–行単位の部品のサイズを計算する
  
set aXsize to xSize
  
set aYsize to ySize / paraCount
  
  
–行単位でループ
  
set aCount to 0
  
repeat with i in aList
    set j to contents of i
    
    
if length of j > 1 then –改行のみの行をスキップ(手作業をしていると先頭とか終末に不意に入ってしまうことがある。それを無視する処理)
      
      
–テキスト各行の冒頭に数字でページ数が振ってあるものと想定
      
set pNum to first word of j
      
set linkTargCanvas to contents of item (pNum as number) of canvasList
      
      
–座標計算
      
set tmpX to xPos
      
set tmpY to yPos + (aYsize * aCount)
      
      
–テキストボックス(自称)を作成(OmniGraffleにそんなオブジェクトはない。みんな図形)
      
tell canvas of front window
        set aShape to make new shape at end of graphics with properties {stroke color:{0.0, 0.0, 0.0}, thickness:0, fill:no fill, draws shadow:false, origin:{tmpX, tmpY}, size:{aXsize, aYsize}, text:{text:j, font:aFontName, size:aFontSize, alignment:left}}
        
        
–jumpは、shapeを作成したあとで指定する必要があった(作成時にpropertyで一緒に突っ込むと無視された)
        
set (jump of aShape) to linkTargCanvas
      end tell
      
    end if
    
    
set aCount to aCount + 1
    
  end repeat
  
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/06 Canvasの大きさを取得(Pointで返る)

OmniGraffleで、現在表示中のCanvasの大きさを取得するAppleScriptです。

結果は、Pointで返ります。

スクリプト名:Canvasの大きさを取得(Pointで返る)
tell application "OmniGraffle 5"
  tell canvas of front window
    set {xCanvasSize, yCanvasSize} to page size –結果はPointで返る
    
–> {1155.0, 783.0}
    
  end tell
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/06 選択中のテキストから情報を取得

OmniGraffleで、選択中のテキストから情報を取得するAppleScriptです。

om30.jpg

スクリプト名:選択中のテキストから情報を取得
tell application "OmniGraffle 5"
  tell front window
    set aSel to selection
    
set aaSel to first item of aSel
    
    
set aText to text of aaSel
    
–> "ぴよ〜"
    
    
set {xSize, ySize} to size of aaSel
    
–> {427.0, 216.0}
    
set {xPos, yPos} to origin of aaSel
    
–> {173.785186767578, 144.184143066406}
    
    
set aFontName to font of text of aaSel
    
–> "HiraKakuProN-W3"
    
set aFontSize to size of text of aaSel
    
–> 144.0
  end tell
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/03 リスト項目のシャッフル

リスト項目のシャッフルを行うAppleScriptです。

「リスト項目のシャッフル」という処理自体は、さほど出現頻度の高いものではありません。だいたい、私がいままで作ったことがなかったぐらいなので、この先も作る必要に迫られる可能性は低そうです。

この手の処理は、1項目呼び出すたびにランダム取り出しをするようなケースが多いので、リスト内容をすべて一括でシャッフルするような処理を書くケースは少ないでしょう。

ただ、それでも「ない」とも言い切れないので試しに組んでみました。予想行数は10行ぐらいだったのですが、予想を大幅に超え、書いているうちにトンでもない行数になってしまいました。

当初は投機的にランダムに項目を抽出して、抽出ずみリストに存在する場合には選び直し……という処理を行っていたのですが(→ 投機的処理)、3,000項目ぐらいのリストなら我慢できたものの、5,000項目を超えると遅く感じられました(MacBook Pro 2010 Core i7 2.66GHz+Mac OS X 10.6.8+日常的なアプリケーション立ち上げまくり環境にて検証)。

投機的処理がどのぐらい「無駄な選び直し」を行っているかを調べてみたところ、だいたい与えた項目数の5〜10倍ぐらい無駄な処理を行っていました。この「無駄」がなければ5〜10倍ぐらいは高速に処理できる可能性があったわけです。

そこで、いいかげんに書かないで地道に処理を行ってみたところ、投機的な処理の3倍ぐらいは高速になりました(→ 地道処理)。頻繁にリストの分割/連結を行うので、このあたりのオーバーヘッドが大きそうですが、それでもアイテム数の回数分しかループを回さないので無駄が少なくてすんでいます。

g1.jpg

t1.jpg

ちなみに、投機的処理を無理矢理「a reference to」による(なげやりな)高速化を施してみたところ、処理時間はずいぶんと改善はしたものの、それでも地道処理に及ばなかったので、地道処理を採用するのがよいのでしょう。

g2.jpg

t2.jpg

めんどくさかったので、地道処理を高速化させるパターンは試しませんでした。

一般的に、AppleScriptでリスト型変数を使って処理を行う場合には、だいたい5,000項目のデータ数がひとつの目安であり、それを超える場合には高速化処理を真剣に検討する必要があります。つまり、ズボラに組むときには、あまり大量のデータ処理を行わないことが重要、ということです。

スクリプト名:リスト項目のシャッフル(投機的処理)
–テスト用に1〜1000までの数字のリストを作成
set aList to {}
repeat with i from 1 to 10000
  set the end of aList to i
end repeat

set t1 to current date
set aRes to length of (retShuffledList(aList) of me)
set t2 to current date

return (t2 - t1)

on retShuffledList(aList)
  
  
–出力用リスト
  
set bList to {}
  
  
set aMax to length of aList
  
set curCount to 0
  
  
repeat
    –ランダム抽出および出力用リスト内の存在確認
    
set anItem to contents of some item of aList –項目のランダム抽出
    
if anItem is not in bList then
      set the end of bList to anItem
      
set curCount to curCount + 1
    end if
    
    
–終了?
    
if curCount = aMax then
      exit repeat
    end if
    
  end repeat
  
  
return bList
  
end retShuffledList

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

スクリプト名:リスト項目のシャッフル(地道処理)
–テスト用に1〜1000までの数字のリストを作成
set aList to {}
repeat with i from 1 to 10000
  set the end of aList to i
end repeat

set t1 to current date
set aRes to length of (retShuffledList(aList) of me)
set t2 to current date

return (t2 - t1)

on retShuffledList(origList)
  
  
copy origList to aList
  
  
  
–出力用リスト
  
set bList to {}
  
  
set aMax to length of aList
  
  
repeat aMax times
    –ランダム抽出および出力用リスト内の存在確認
    
set curLen to (length of aList)
    
if curLen > 1 then
      –通常処理
      
set aRandom to random number from 1 to curLen
      
set anItem to contents of item aRandom of aList
      
      
set the end of bList to anItem
      
      
set aList to delListItem(aList, aRandom) of me
    else
      –最後の項目の場合の例外処理
      
set the end of bList to contents of first item of aList
    end if
  end repeat
  
  
return bList
  
end retShuffledList

–リストから指定のアイテムを削除する
on delListItem(aList, anItemNo)
  set newList to {}
  
  
set aLen to length of aList
  
if anItemNo = 1 then
    set newList to contents of items 2 thru -1 of aList
  else if anItemNo = aLen then
    set newList to contents of items 1 thru -2 of aList
  else
    set p1List to contents of items 1 thru (anItemNo - 1) of aList
    
set p2List to contents of items (anItemNo + 1) thru -1 of aList
    
set newList to p1List & p2List
  end if
  
  
return newList
  
end delListItem

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

スクリプト名:リスト項目のシャッフル(投機的処理) +高速化
global aList, aList_r
global aaaList, aaaList_r
global bList, bList_r

–テスト用に1〜1000までの数字のリストを作成
set aList to {}
set aList_r to a reference to aList

repeat with i from 1 to 10000
  set the end of aList_r to i
end repeat

set t1 to current date
set aRes to length of (retShuffledList(aList_r) of me)
set t2 to current date

return (t2 - t1)

on retShuffledList(aaList)
  
  
copy aaList to aaaList
  
set aaaList_r to a reference to aaaList
  
  
–出力用リスト
  
set bList to {}
  
set bList_r to a reference to bList
  
  
set aMax to length of aaaList_r
  
set curCount to 0
  
  
repeat
    –ランダム抽出および出力用リスト内の存在確認
    
set anItem to contents of some item of aaaList_r –項目のランダム抽出
    
if anItem is not in bList_r then
      set the end of bList_r to anItem
      
set curCount to curCount + 1
    end if
    
    
–終了?
    
if curCount = aMax then
      exit repeat
    end if
    
  end repeat
  
  
return bList
  
end retShuffledList

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/03 リストから指定のアイテムを削除する(1項目)

指定リストから指定番号のアイテムを削除するAppleScriptです。

ここで言う「指定番号」とは、アイテム番号のことです。

書き捨てレベルのものなので、よく書いてしまうのですが……何らかの巨大なAppleScriptの必須部品として利用するものの、それ自体をsaveするほどでもないので本Blogに掲載していなかったという……。

スクリプト名:リストから指定のアイテムを削除する(1項目)

set aList to {1, 2, 3, 4, 5}
set bList to delListItem(aList, 4) of me
–> {1, 2, 3, 5}

–リストから指定のアイテムを削除する
on delListItem(aList, anItemNo)
  set newList to {}
  
  
set aLen to length of aList
  
if anItemNo = 1 then
    set newList to contents of items 2 thru -1 of aList
  else if anItemNo = aLen then
    set newList to contents of items 1 thru -2 of aList
  else
    set p1List to contents of items 1 thru (anItemNo - 1) of aList
    
set p2List to contents of items (anItemNo + 1) thru -1 of aList
    
set newList to p1List & p2List
  end if
  
  
return newList
  
end delListItem

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/03 OmniGraffleで各ページに用意したtitleから、目次を作成する

OmniGraffleで、各ページ(Canvas)上に用意したtitleから、目次を作成するAppleScriptです。

OmniGraffleで仕様書を作成したときに、目次の自動生成も行えないと不便です。しかし、OmniGraffleにはタイトル用の専用オブジェクトなどという便利なものはありません。

そこで、なければ自分で勝手に作ってしまおうというわけです。

各ページ(Canvas)上に「URL」に「title」という文字を指定したテキストのオブジェクトを配置し、そこに各ページの題名を入れておくことにしました。これを(勝手に)タイトル用のオブジェクトとみなすことにしました。

本AppleScriptはこの各ページのtitleの文字列を読み取ってページ数と組み合わせて目次のテキストデータをクリップボードに転送します。以前にKeynote用に作成しておいたものをほぼそのままOmniGraffle用に転用しています。

スクリプト名:各ページに用意したtitleから、目次を作成する
set separatorC to “(” –「(1/3)」の形式で連番を振っている場合の最初のカッコを書いておく。ここでは、全角カッコを想定

–OmniGraffleでオープン中のドキュメントが存在するかを確認
tell application “OmniGraffle 5″
  set wCount to count every window –本来ならdocumentオブジェクト経由で確認したいところだが、OmniGraffleのdocumentの仕様がおかしい
  
if wCount = 0 then
    display dialog “ドキュメントがオープンされていません” buttons {“OK”} default button 1 with icon 1
    
return
  end if
end tell

–OmniGraffleの各最前面のドキュメントの各Stencilのタイトル(URLが”title”になっているもののtext)を取得する
set aList to retIndexDataFromFrontDocument() of me

–各タイトルにすでに「(1/2)」のようなナンバリングが行われていたらナンバリング部分を削除する
set newList to {}

repeat with i in aList
  set j to contents of i
  
  
set aPos to offset of separatorC in j
  
if aPos is not equal to 0 then
    set j to text 1 thru (aPos - 1) of j
  end if
  
  
set the end of newList to j
end repeat

–目次を作成する
set sPage to 3 –ページ数カウンタ
set curItem to second item of newList
set newList2 to items 3 thru -1 of newList

set outList to {{2, curItem}} –出力用データ

repeat with i in newList2
  set j to contents of i
  
  
if j = curItem then
    –何もしない
  else
    set curItem to j
    
set the end of outList to {sPage, j}
  end if
  
  
set sPage to sPage + 1
  
end repeat

set aText to retItemDelimedAndParagraphDelimedText(outList, tab, return) of me
set the clipboard to aText

–最前面のドキュメントからインデックス用データを取得する
on retIndexDataFromFrontDocument()
  
  
tell application “OmniGraffle 5″
    tell front document
      set cList to every canvas
      
      
set pCount to 1
      
set pList to {}
      
      
repeat with i in cList
        set j to contents of i
        
        
tell j
          set gList to (every graphic whose url is “title”)
          
          
if gList is not equal to {} then
            set aGraphic to contents of first item of gList
            
set aText to text of aGraphic
          end if
        end tell
        
        
–set the end of pList to {pCount, aText}
        
set the end of pList to aText
        
        
set pCount to pCount + 1
        
      end repeat
    end tell
  end tell
  
  
return pList
  
end retIndexDataFromFrontDocument

–入れ子のリストを、アイテム間のデリミタとパラグラフ間のデリミタを指定してテキスト化
–というか、入れ子のリストをタブ区切りテキストにするのが目的
on retItemDelimedAndParagraphDelimedText(aList, itemDelim, paragraphDelim)
  set aText to “”
  
  
repeat with i in aList
    set aStr to retDelimedText(i, itemDelim) of me
    
set aText to aText & aStr & paragraphDelim
  end repeat
  
  
return aText
end retItemDelimedAndParagraphDelimedText

on retDelimedText(aList, aDelim)
  set aText to “”
  
set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to aDelim
  
set aText to aList as text
  
set AppleScript’s text item delimiters to curDelim
  
return aText
end retDelimedText

–OmniGraffleですべてのWindowを表示状態に
on exposeEveryWindow()
  tell application “OmniGraffle 5″
    tell (every window whose miniaturized is true)
      set miniaturized to false
    end tell
  end tell
end exposeEveryWindow

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

12/03 Safari 5.1.2、AppleScript用語辞書に変更なし

2011.12.1にSafari 5.1.2がソフトウェアアップデートから入手可能になっていましたが、AppleScript用語辞書に変更はありませんでした。