Archive for 1月, 2010

2010/01/30 ちょっと気の利いた複数folder選択

仕事用にかなり凝ったAppleScriptのプログラムを作成したような場合には、処理対象のデータを自由に指定できるよう、処理先をchoose folderやchoose fileで指定することがあります。プログラム内にパスを固定で記述するよりも自由度が高く、ユーザーフレンドリーな処理になります。

ただ……1つのAppleScript中でいくつもフォルダを選択する必要があったりすると、そのたびにフォルダ選択ダイアログで処理対象のフォルダを指定しなければなりません。しかも、Folder Aを選択したあとでFolder Bを選択しようとすると、Folder Aの場所からスタートしなくてはなりません。

これはけっこう面倒です。

dialog1.jpeg

dialog2.jpeg

そこで、choose folderで一度フォルダを選択したら、propertyの値に保持しておいて、各フォルダ選択ダイアログで個別に記録。二度目からはそれぞれ前回指定したフォルダを勝手に指定するよう、些細な改善を行ってみました。

その結果、大幅な操作性の改善を行うことができ、いくつフォルダ選択ダイアログが出てきても、うっとおしくなくなりました(たいていにおいて、前回と同じ場所を指定することのほうが多く、目視で確認してリターンキーを押していくだけ)。

スクリプト名:ちょっと気の利いた複数folder選択
property folderA : missing value
property folderB : missing value

if folderA is not equal to missing value then
  –前回の選択内容を使用
  
set folderA to choose folder default location folderA with prompt “folder Aを選択”
else
  –はじめて実行する場合など
  
set folderA to choose folder with prompt “folder Aを選択”
end if

if folderB is not equal to missing value then
  –前回の選択内容を使用
  
set folderB to choose folder default location folderB with prompt “folder Bを選択”
else
  –はじめて実行する場合など
  
set folderB to choose folder with prompt “folder Bを選択”
end if

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

2010/01/27 lengthの同義語number of items

AppleScriptで配列変数(AppleScript的には「リスト型変数」)の要素数をカウントする場合には、「length of リスト型変数」で取得しますが、AppleのサンプルScriptを読んでいたら、「number of items of リスト型変数」という記述があるのを見つけました。

AppleScriptには同義語やら「使っても使わなくてもかまわない語」(theとか)などがあるので、人によってプログラムの書き方がバラバラで、記述の流派が違うとまったくプログラムを読めない(理解できない)場合があります。

とりあえず、numbers of itemsでも要素数をカウントできることは確認しました。

スクリプト名:number of itemsのじっけん(1)
set aList to {1, 2, 3, 4}

set aLen to number of items of aList
–> 4

set bLen to length of aList
–> 4

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

実際のところ、スピードはどうなんでしょう? なんとなく、感覚的にlength ofのほうが速そうな感じはするのですが……この程度の命令だと一瞬で終わってしまうので、1000万回実行してようやく意味のある差が出てきました。

気持ち分length ofのほうが速いですが、そもそも1000万回もループを回すような処理はAppleScriptでは行わないでしょうから、そんなに気にしなくてよさそうです(計測はMacBook Pro Core 2 Duo 2.4GHzにて実施)。

スクリプト名:number of itemsのじっけん(2)
set aList to {1, 2, 3, 4}

set sTime to current date

repeat 10000000 times
  set bLen to length of aList
end repeat

set eTime to current date
set elapsedTime to eTime - sTime

–1000万回ループ時に
–number of items で23秒
–length で14秒

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

2010/01/22 元号変換v31

以前作成したものに(派手な)バグが見つかったので、修正しました。Wikipediaで確認していたつもりだったのですが、元号の切れ目の日付を間違えてしまいました。

指摘してくれたのが中国から日本に来ているプログラマーの女性で(汗)、ひたすら感謝です。

スクリプト名:元号変換v31
set a to "1989/1/18"
set a to parseDate(a) of me
set {aGengoStr, aGengoNum} to retJapaneseGengo(a) of me
–> {"平成", 1}

on retJapaneseGengo(aDate)
  
  
set aYear to year of aDate
  
set aMonth to month of aDate as number
  
set aDay to day of aDate
  
  
set aStr to retZeroPaddingText(aYear, 4) of me & retZeroPaddingText(aMonth, 2) of me & retZeroPaddingText(aDay, 2) of me
  
  
set aGengo to ""
  
–if aStr "19890118" then–間違ってた!!!
  
if aStr "19890108" then
    set aGengo to "平成"
    
set aGengoNum to aYear - 1989 + 1
  else if aStr "19261225" then
    set aGengo to "昭和"
    
set aGengoNum to aYear - 1926 + 1
  else if aStr "19120730" then
    set aGengo to "大正"
    
set aGengoNum to aYear - 1912 + 1
    
–else if aStr "18681125" then–間違ってた!!!
  else if aStr "18680125" then
    set aGengo to "明治"
    
set aGengoNum to aYear - 1868 + 1
  end if
  
  
return {aGengo, aGengoNum}
  
end retJapaneseGengo

–数値にゼロパディングしたテキストを返す
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

on parseDate(inStr)
  set aClass to class of inStr
  
if aClass = string then
    try
      set aDate to date inStr
    on error
      return false
    end try
  else if aClass = date then
    set aDate to inStr
    
  end if
  
  
return aDate
  
end parseDate

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

2010/01/17 リストから選択してアイテム番号を返す〜複数選択対応

choose from listで複数選択を行いつつ、選択した項目がそれぞれリストの何アイテム目かの情報を返すAppleScriptです。

以前に、複数選択ではないバージョンは作ってあったのですが、Omni Outlinerから選択部分を取得するAppleScriptを作成する際に、即興で作ったものです。

スクリプト名:リストから選択してアイテム番号を返す〜複数選択対応
set aList to {“red”, “blue”, “green”, “white”}
set aMes to “項目を選択してください(Command-クリックで複数選択可能)”
set aRes to retMultipleItemFromListByItemNo(aList, aMes) of me

–リストから選択してアイテム番号を返す(複数項目選択対応)
on retMultipleItemFromListByItemNo(aList, aMes)
  set aRes to choose from list aList with prompt aMes with multiple selections allowed
  
if aRes = false then return 0
  
  
set hitList to {}
  
repeat with i1 in aRes
    set aRes to contents of i1
    
set hitNum to 1
    
repeat with i in aList
      set j to contents of i
      
if j is equal to aRes then
        exit repeat
      end if
      
set hitNum to hitNum + 1
    end repeat
    
set the end of hitList to hitNum
  end repeat
  
return hitList
end retMultipleItemFromListByItemNo

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

2010/01/17 OniOutlinerで選択中の行の内容のうち指定列のデータをすべて取得

OmniOutliner Professional v3.9.5で(通常のOmni Outlinerもtellブロックのアプリ名称を変更すればそのまま動作)、選択中の行のデータの中から指定列(カラム)のデータをすべて取得するAppleScriptです。

つまり、選択行の中の指定カラムのデータを取得するものです。こうした機能が標準搭載されていないほうがおかしいような気がします。そのままGUI上でExcelに選択データをペーストすると、1つの列の中にすべてのデータが入ってしまうなど、動作内容がいまいちです。

そこで、AppleScriptの登場となるわけで……旅行からの帰りの電車の中で勢いだけで作ってしまいました。

まずは、Onni Outliner上で行を選択。

omni1.jpg

スクリプトを実行すると、どの列のデータを取得するか聞いてきます。Command-クリックを行うことで、複数の列のデータを指定できます。

omni2.jpg

ためしに、「トピック」列と「Column2」列を選択すると……

{{”line2″, “”}, {”line3″, “●”}, {”line4″, “●”}, {”line5″, “”}}

といった結果が得られます。これは、Excelに貼付けることを前提としてデータを作成しているものであり、あとはExcelへのデータ貼り付けルーチンを作って用意すれば、便利に使えそうです。

excel1.jpg

……結局、Excelのワークシートを新規作成して、データ転送するところまで作り込んでしまいました。ここまでやると、たしかに便利。

スクリプト名:OniOutlinerで選択中の行の内容のうち指定列のデータをすべて取得
global tList, numRes
set tList to {}

tell application “OmniOutliner Professional”
  tell document 1
    set aSel to every row whose selected is true
    
if aSel = {} then
      display dialog “OmniOutliner上で選択されている行はありませんでした。” buttons {“OK”} default button 1
      
return
    end if
  end tell
end tell

–どの列を書き出すかを選択
tell application “OmniOutliner Professional”
  tell document 1
    set nList to name of every column
    
set aMes to “項目を選択してください(Command-クリックで複数選択可能)”
    
set numRes to retMultipleItemFromListByItemNo(nList, aMes) of me
  end tell
end tell

–選択部分を検出
tell application “OmniOutliner Professional”
  tell document 1
    set aaSel to first item of aSel
    
tell aaSel
      set aCell to text of every cell
      
set aCell to retSpecifiedItemFromList(numRes, aCell) of me
      
set the end of tList to aCell
      
set cList to every child
      
repeat with i in cList
        getChildText(i) of me
      end repeat
    end tell
  end tell
end tell

tList
–> {{”line2″, “”}, {”line3″, “●”}, {”line4″, “●”}, {”line5″, “”}}

–再帰で指定child以下のテキストをすべて取得する
on getChildText(aRoot)
  tell application “OmniOutliner Professional”
    tell document 1
      tell aRoot
        set aCell to text of every cell
        
set aCell to retSpecifiedItemFromList(numRes, aCell) of me
        
set the end of tList to aCell
        
        
set cList to every child
        
repeat with i in cList
          getChildText(i) of me –再帰処理
        end repeat
      end tell
    end tell
  end tell
end getChildText

–リストから選択してアイテム番号を返す(複数項目選択対応)
on retMultipleItemFromListByItemNo(aList, aMes)
  set aRes to choose from list aList with prompt aMes with multiple selections allowed
  
if aRes = false then return 0
  
  
set hitList to {}
  
repeat with i1 in aRes
    set aRes to contents of i1
    
set hitNum to 1
    
repeat with i in aList
      set j to contents of i
      
if j is equal to aRes then
        exit repeat
      end if
      
set hitNum to hitNum + 1
    end repeat
    
set the end of hitList to hitNum
  end repeat
  
return hitList
end retMultipleItemFromListByItemNo

–指定リストから、指定アイテム目を抽出して返す
on retSpecifiedItemFromList(itemNumList, dataList)
  set newList to {}
  
repeat with i in itemNumList
    set j to contents of i
    
set a to contents of (item j of dataList)
    
set the end of newList to a
  end repeat
  
return newList
end retSpecifiedItemFromList

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

2010/01/11 UnicodeCheckerで指定文字のプロパティを取得

UnicodeCheckerで指定文字のプロパティを取得するAppleScriptです。

文字種別を取得できる機能は、なにげにものすごく便利です。

ucheck1.jpg

スクリプト名:UnicodeCheckerで指定文字のプロパティを取得
set a to "" –はしご高 ( Kanji Character )
set b to "A" –alphabet A
set c to "あ" –ひらがな

tell application "UnicodeChecker"
  set aCP to code point a
  
properties of aCP
  
–> {assigned:true, canonical combining class:0, canonical combining class description:"Not_Reordered", id:39641, general category:"Lo", unicode name:"<CJK Ideograph>", class:code point, general category description:"Other_Letter", assigned to abstract character:true, bidi class description:"Left_To_Right", bidi class:"L", name:"", containing plane:plane id 0 of application "UnicodeChecker", bidi mirrored:false, script name:"Han", containing block:block "CJK Unified Ideographs" of application "UnicodeChecker"}
  
  
set bCP to code point b
  
properties of bCP
  
–> {assigned:true, canonical combining class:0, canonical combining class description:"Not_Reordered", id:65, general category:"Lu", unicode name:"LATIN CAPITAL LETTER A", class:code point, general category description:"Uppercase_Letter", assigned to abstract character:true, bidi class description:"Left_To_Right", bidi class:"L", name:"A", containing plane:plane id 0 of application "UnicodeChecker", bidi mirrored:false, script name:"Latin", containing block:block "Basic Latin" of application "UnicodeChecker"}
  
  
set cCP to code point c
  
properties of cCP
  
–> {assigned:true, canonical combining class:0, canonical combining class description:"Not_Reordered", id:12354, general category:"Lo", unicode name:"HIRAGANA LETTER A", class:code point, general category description:"Other_Letter", assigned to abstract character:true, bidi class description:"Left_To_Right", bidi class:"L", name:"あ", containing plane:plane id 0 of application "UnicodeChecker", bidi mirrored:false, script name:"Hiragana", containing block:block "Hiragana" of application "UnicodeChecker"}
end tell

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

2010/01/11 IGOR Proでコマンドを実行

WaveMetrics社が開発し、日本国内ではヒューリンクスが販売している、データ解析+ビジュアライズ用のソフトウェア「IGOR Pro」をAppleScriptからコントロールしてみました(掲載されているサンプルそのまま。変更点なし)。

AppleScriptからアプリケーションの各種情報を取得できるようにはなっておらず、Igor Proのコマンドや関数を使って情報を取得するようです(R.appなどと同じタイプ)。

igor1.jpg

スクリプト名:Igor Proでコマンドを実行
tell application “Igor Pro”
  Do Script “fprintf 0, \”%s\”, IgorInfo(1)”
end tell
–> “3DRotationsPanel”

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

2010/01/11 LightWay TextをAppleScriptからコントロール

LightWay TextをAppleScriptからコントロールしてみました。

縦書き可能だったり、原稿用紙に記入するようにテキスト編集可能な老舗のテキストエディタLightWay Textですが、AppleScript系の機能については、残念ながら不完全かつ不十分であることを確認しました。

lwt.jpg

AppleScriptとの連携を考えるのであれば、Apple純正のテキストエディット、Jedit X、mi、BBeditやTextWranglerなどを使うのが適切でしょう。

以下、LightWay Textのコントロールを試みた内容です。

テキストの内容を取得できますが、そのままでは扱えないようです。

スクリプト名:LightWay Textで文章の内容を取得
tell application “LightWayText”
  tell document 1
    contents
  end tell
end tell
–>
(*

{”ぴよ〜うるふ
ぴよぴよ”, «data styl01000000000015000C00019000000E00000000000000», «data styw000100000000000000000000»}
*)

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

ドキュメントの内容をas stringでcastすれば、一応内容(contents)を取得できます。また、同様にSelectionの内容も、そのままでは使用できません。Application、Document、Windowといった主要なオブジェクトのプロパティも一切取得できません。できないことだらけです。

スクリプト名:LightWay Textで文章の選択部分を取得
tell application “LightWayText”
  tell document 1
    set a to selection
  end tell
end tell
–>
(*
{”ぴよぴよ”,
«data styl01000000000015000C00019000000E00000000000000», «data styw000100000000000000000000»}
*)

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

文章内の選択部分(selection)をas stringでcastすると文字列として取得できますが、逆に選択範囲の内容を書き換えることができません。

Selectionを介してAppleScriptとインタフェースを行うという、一般的なMac系のテキストエディタのような運用はできないことが分りました。無理矢理、GUI Scripting経由でアクセスすることは不可能ではありませんが、動作の確実性を保証できない(最前面にLightWay Textがいないと、メニューを操作して確実にPaste動作を行わせられない)ところです。

スクリプト名:LightWay Textで選択部分を文字列として取得
tell application “LightWayText”
  tell document 1
    set a to selection as string
  end tell
end tell
–> “ぴよぴよ”

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

2010/01/04 動的に演算子を指定してリストから要素を取り出す

動的に「starts with」「ends with」「contains」と演算子を指定して、指定のリストから要素を取り出すAppleScriptです。

ありあわせの「リストをテキストに」するサブルーチンを引っ張り出してきて、テレビを見ながら15分ぐらいで作りました。

AppleScript Users MLでそういうことができないか、と質問が出て「できない」「そりゃ無理」と返事が続いていたので、さくっと組んで投稿したものです。

たしかに、言語仕様上では無理な処理ですが……こうしてダイナミックに(動的に)文字列を組み立ててrun scriptコマンドを実行すれば、たいていの無茶な内容も実現できます。そこまで組む根性があるかどうか。めんどうくさいと思うかどうかで「できない」が「できる」に変わります。この場合は、リストをテキスト化するサブルーチンのストックがあることを覚えていたのですぐに作れました。

ただ、これにどのぐらいの利用価値があるか、個人的にははてしなく疑問です。

スクリプト名:動的に演算子を指定してリストから要素を取り出す
set myVar to {“John Smith”, “Mike Smith”, “Michael Jackson”}
set myArray to {myVar, “ends with”, “Smith”}
set aList to retDynamicOperatedData(myArray) of me
–> {”John Smith”, “Mike Smith”}

on retDynamicOperatedData(myData)
  try
    set aVar to contents of item 1 of myData
    
set operatorCmd to contents of item 2 of myData
    
set operatorStr to contents of item 3 of myData
  on error
    return {}
  end try
  
  
–Command Check
  
if operatorCmd is not in {“starts with”, “ends with”, “contains”} then return {}
  
  
–Make Dynamic Script  
  
set aScript to “set aList to “ & listToText(aVar) of me &
  set rList to {}
  repeat with i in aList
  set j to contents of i
  if j “
& operatorCmd & ” \”" & operatorStr & “\” then
  set the end of rList to j
  end if
  end repeat
  return rList
  ”

  
try
    set aRes to (run script aScript)
  on error
    set aRes to {}
  end try
  
  
return aRes
  
end retDynamicOperatedData

–List to String Sub-routine
on listToText(aList)
  set listText to {“{”}
  
set quotChar to ASCII character 34
  
set firstFlag to true
  
repeat with i in aList
    set j to contents of i
    
set aClass to class of i
    
if (aClass = integer) or (aClass = number) or (aClass = real) then
      set the end of listText to (getFirst(firstFlag) of me & j as text)
      
set firstFlag to false
    else if (aClass = string) or (aClass = text) or (aClass = Unicode text) then
      set the end of listText to ((getFirst(firstFlag) of me & quotChar & j as text) & quotChar)
      
set firstFlag to false
    else if aClass is list then
      set the end of listText to (getFirst(firstFlag) of me & listToText(j)) –recursive call
      
set firstFlag to false
    end if
  end repeat
  
set the end of listText to “}”
  
set listText to listText as text
  
return listText
end listToText

on getFirst(aFlag)
  if aFlag = true then return “”
  
if aFlag = false then return “,”
end getFirst

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

2010/01/04 指定形式でスクリーンキャプチャを撮る

指定形式(JPEG、PNG、GIFF、PDF、TIFF)で、指定フォルダ内にファイル名YYMMDDHMSSでスクリーンキャプチャを撮影するAppleScriptです。

screencaptureコマンドはGIFFでもスクリーンキャプチャを撮れるのですが、GIFFを指定する必然性はかなり低いことでしょう。

スクリーンキャプチャをAppleScript内で撮れると、エラーが起こった時に撮ってメールに添付して所定のアドレスに送信するなど、エラーレポートに威力を発揮するほか、ほかにも「いいこと」がいろいろあります。とくに、無人の環境で長時間連続運転を行っているような場合には、こうした配慮が必要です。

さらに、撮ったあとのレポート方法についても、バカの1つ覚えでメールに添付して送るだけでなく、Dropboxにアップロードするとか(AppleScriptから見るとローカルのPublicフォルダにコピーするだけなので、ものすごく簡単)、各種Webサービスにアップロードするなどの手段が使用でき、選択の幅が広がってきました。

もちろん、古典的にプリンターやFAXに送って印刷してもいいですし、(なぜか)Excelのワークシートに貼り付けてZip圧縮してメールに添付して送信してもよいでしょう。

スクリプト名:指定形式でスクリーンキャプチャを撮る
set a to choose folder
set aRes to screenCapture(a, “JPEG”) of me
–> “/Users/xxxx/Desktop/20100104155956.JPEG”–正常終了の場合。エラー時はfalse

–指定形式でスクリーンキャプチャを指定フォルダに撮る
on screenCapture(aFolder, aType)
  set aFolPOSIX to POSIX path of aFolder
  
set todayStr to do shell script “date +%Y%m%d%H%M%S” –YYMMDDHHMMSS
  
  
–大文字小文字を無視、ピリオドを無視
  
ignoring case and punctuation
    if aType is not in {“JPEG”, “PNG”, “GIFF”, “PDF”, “TIFF”} then
      return false –エラー
    end if
  end ignoring
  
  
–拡張子部分を小細工する
  
if aType begins with “.” then
    set aExt to aType
    
set aType to text 2 thru -1 of aType
  else
    set aExt to “.” & aType
  end if
  
  
set aPathPOSIX to (aFolPOSIX & todayStr & aExt)
  
set aPathPOSIXq to quoted form of aPathPOSIX
  
set sText to “screencapture -xt” & aType & ” “ & aPathPOSIXq
  
  
do shell script sText
  
return aPathPOSIX
  
end screenCapture

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

2010/01/02 あけましておめでとうございます

新年あけましておめでとうございます。今年も、自作のアプリケーション「おかえり」で、MacのSleep解除時におかえりメッセージを表示。正月3が日の間は「謹賀新年」「あけましておめでとうございます」メッセージを表示しています。対応OSはMac OS X 10.4〜10.6。

okaeri.jpg

「おかえり」の前バージョンが2009年12月31日までの使用期限を設定してあったため、若干の機能アップを行った新バージョンを公開しました。本アプリもすべてAppleScript Studioで作成したものです(透明ウィンドウの部分のみObjective-C)。