Archive for 12月, 2009

2009/12/30 指定日に在任中の内閣総理大臣の氏名を取得 v2

以前に作ったサブルーチンのデータを最新のものにアップデートしたものです。YYYY/MM/DD形式の日付文字列を与えると、その時に在任中の内閣総理大臣の氏名を返します。

スクリプト名:指定日に在任中の内閣総理大臣の氏名を取得 v2
set aDateStr to “2009/03/14″
set aName to retPrimeMinisterInJapan(aDateStr) of me
–> “麻生太郎”

–指定日に在任中の内閣総理大臣の氏名を取得
–【参照】 http://ja.wikipedia.org/wiki/内閣総理大臣の一覧
on retPrimeMinisterInJapan(aDate)
  –議院内閣制成立以前ならfalse
  
set lowDate to “1885/12/22″
  
if aDate < date lowDate then
    display dialog “まだ議院内閣制がはじまっていない時代です。”
    
return false
  end if
  
  
set primeList to {{“伊藤博文”, “1885/12/22″}, {“黒田清隆”, “1888/04/30″}, {“三條實美”, “1889/10/25″}, {“山縣有朋”, “1889/12/24″}, {“松方正義”, “1891/05/06″}, {“伊藤博文”, “1892/08/08″}, {“黒田清隆(臨時兼任)”, “1896/08/31″}, {“松方正義”, “1896/09/18″}, {“伊藤博文”, “1898/01/12″}, {“大隈重信”, “1898/06/30″}, {“山縣有朋”, “1898/11/8″}, {“伊藤博文”, “1900/10/19″}, {“西園寺公望(臨時兼任)”, “1901/05/10″}, {“桂太郎”, “1901/06/02″}, {“西園寺公望”, “1906/01/07″}, {“桂太郎”, “1908/07/14″}, {“西園寺公望”, “1911/08/30″}, {“桂太郎”, “1912/12/21″}, {“山本權兵衞”, “1913/02/20″}, {“大隈重信”, “1914/04/16″}, {“寺内正毅”, “1916/10/09″}, {“原敬”, “1918/09/29″}, {“内田康哉(臨時兼任)”, “1921/11/04″}, {“高橋是清”, “1921/11/13″}, {“加藤友三郎”, “1922/06/12″}, {“内田康哉(臨時兼任)”, “1923/08/24″}, {“山本權兵衞”, “1923/09/02″}, {“若槻禮次郎”, “1926/01/30″}, {“田中義一”, “1927/04/20″}, {“濱口雄幸”, “1929/07/02″}, {“若槻禮次郎”, “1931/04/14″}, {“犬養毅”, “1931/12/13″}, {“高橋是清(臨時兼任)”, “1932/05/16″}, {“齋藤實”, “1932/05/26″}, {“岡田啓介”, “1934/07/08″}, {“廣田弘毅”, “1936/03/09″}, {“林銑十郎”, “1937/02/02″}, {“近衞文麿”, “1937/06/04″}, {“平沼騏一郎”, “1939/01/05″}, {“阿部信行”, “1939/08/30″}, {“米内光政”, “1940/01/16″}, {“近衞文麿”, “1940/07/22″}, {“東條英機”, “1941/10/18″}, {“小磯國昭”, “1944/7/22″}, {“鈴木貫太郎”, “1945/04/07″}, {“東久邇宮稔彦王”, “1945/08/17″}, {“幣原喜重郎”, “1945/10/09″}, {“吉田茂”, “1946/05/22″}, {“片山哲”, “1947/05/24″}, {“芦田均”, “1948/03/10″}, {“吉田茂”, “1948/10/15″}, {“鳩山一郎”, “1954/12/10″}, {“石橋湛山”, “1956/12/23″}, {“岸信介”, “1957/02/25″}, {“池田勇人”, “1960/07/19″}, {“佐藤榮作”, “1964/11/09″}, {“田中角榮”, “1972/07/07″}, {“三木武夫”, “1974/12/09″}, {“福田赳夫”, “1976/12/24″}, {“大平正芳”, “1978/12/07″}, {“伊東正義(臨時代理)”, “1980/06/12″}, {“鈴木善幸”, “1980/07/17″}, {“中曾根康弘”, “1982/11/27″}, {“竹下登”, “1987/11/06″}, {“宇野宗佑”, “1989/06/03″}, {“海部俊樹”, “1989/08/10″}, {“宮澤喜一”, “1991/11/05″}, {“細川護熙”, “1993/08/09″}, {“羽田孜”, “1994/04/28″}, {“村山富市”, “1994/06/30″}, {“橋本龍太郎”, “1996/01/11″}, {“小渕恵三”, “1998/07/30″}, {“森喜朗”, “2000/04/05″}, {“小泉純一郎”, “2001/04/26″}, {“安倍晋三”, “2006/09/26″}, {“福田康夫”, “2007/09/26″}, {“福田康夫”, “2007/09/26″}, {“麻生太郎”, “2008/09/24″}, {“鳩山由紀夫”, “2009/09/16″}}
  
  
set prList to reverse of primeList
  
  
set hitF to false
  
repeat with i in prList
    set aPrimeDate to contents of (item 2 of i)
    
if aDate > aPrimeDate then
      set hitF to true
      
exit repeat
    end if
  end repeat
  
  
if hitF = false then return false
  
  
return contents of (item 1 of i)
  
end retPrimeMinisterInJapan

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

2009/12/30 伏せ字文字列を返す

伏せ字文字列を返すAppleScriptです。

以前にAppleScriptエディタ上の選択部分の文字数をカウントして、同一の文字数の「X」に置換するAppleScriptを作成しましたが、それをさらに高機能にすべく作成したのがこれです。

0〜9、A〜Z、a〜zを個別に別々の文字(9、X、x)に置換します。このため、「03-1234-5678」は「99-9999-9999」になり、「This is a pen.」は「Xxxx xx x xxx.」になります。

数値とアルファベットの区別が分ってしまうとまずいケースも多々ありますが、なんとなくそれっぽく伏せ字になっていればよい、という場合には気が利いていてよいのではないでしょうか?

スクリプト名:伏せ字文字列を返す
set a to “This is a pen. 03-012345″
set aRes to retMaskedStr(a) of me
–> “Xxxx xx x xxx. 99-999999″

–伏せ字文字列を返す
on retMaskedStr(aStr)
  set aList to characters of aStr
  
set outStr to “”
  
repeat with i in aList
    set j to contents of i
    
set jID to ASCII number of j
    
    
if jID 48 and jID 57 then
      –0〜9
      
set outStr to outStr & “9″
      
    else if jID 65 and jID 90 then
      –A〜Z
      
set outStr to outStr & “X”
      
    else if jID 97 and jID 122 then
      –a〜z
      
set outStr to outStr & “x”
      
    else
      –上記以外はそのまま出力
      
set outStr to outStr & j
      
    end if
  end repeat
  
  
return outStr
end retMaskedStr

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

2009/12/29 1〜9999までの数値を日本語的漢数字表現でエンコード v2

1〜9999までの数値を、「百二十」「九千九百九十九」といった日本語的な漢数字表現でエンコードするAppleScriptです。

以前に、無量大数まで対応した数値→日本語的漢数字表現(およびその逆を行う)のサブルーチンは作ってあったのですが、0〜9999までの数値についてはそのままアラビア数字(0〜9)で出力していました。

enc1.jpg

enc2.jpg

これをすべて漢数字でエンコード出力できるよう作成したものです。

スクリプト名:1〜9999までの数値を日本語的漢数字表現でエンコード v2
set a to 1121
set aKanji to convNumToKanjiNum(a) of me
–> "千百二十一"

–1〜9999までの数値を日本語的漢数字表現でエンコード
on convNumToKanjiNum(aNum)
  –1万以上の場合にはヌルを返す(エラー)
  
if aNum > 9999 then return ""
  
  
copy aNum to bNum
  
  
set aNumList to {}
  
repeat with i from 3 to 0 by -1
    set aDigit to 10 ^ i
    
    
set tmpA to bNum div aDigit
    
set bNum to bNum - (aDigit * tmpA)
    
    
set the end of aNumList to tmpA
    
  end repeat
  
  
set outStr to ""
  
  
set digitStrList to {"千", "百", "十", ""}
  
set kanjiNumList to {"一", "二", "三", "四", "五", "六", "七", "八", "九"}
  
  
repeat with i from 1 to 4
    set cNum to item i of aNumList
    
if cNum is not equal to 0 then
      set cNum1 to contents of item cNum of kanjiNumList
      
set cNum2 to contents of item i of digitStrList
      
      
if cNum = 1 and cNum2 is not "" then
        –「1千」、「1百」、「1十」 というパターンは日本語表現的にはおかしいので補正する(きわめて日本的な慣例的表現。"one thousand"とはいわず、"thousand"というわけだ。ただ、声に出して読み上げるときには「いっせんまん」という)
        
set outStr to outStr & cNum2
      else
        set outStr to outStr & cNum1 & cNum2
      end if
    end if
  end repeat
  
  
return outStr
  
end convNumToKanjiNum

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

2009/12/26 指定文字列の中に指定文字が何回出現するかカウントする

指定文字列の中に指定文字が何回出現するかをカウントするAppleScriptです。

たとえば、「XXX-XXX-XXXXXXX-XXXX」という文字列の中に「-」が何回出現するかを数えたいような場合に使用します。

作り捨てするレベルのAppleScriptなので、何回か作っているかもしれません。

スクリプト名:指定文字列の中に指定文字が何回出現するかカウントする
set aData to “XXX XXXXX XXXXX XX XXXXXX”
set lookUpStr to ” “
set aRes to countSpecifiedChar(aData, lookUpStr) of me
–> 4

–指定文字列の中に指定文字が何回出現するかカウントする
on countSpecifiedChar(aStrData, aChar)
  set aList to characters of aStrData
  
set aCount to 0
  
repeat with i in aList
    set j to contents of i
    
if j = aChar then
      set aCount to aCount + 1
    end if
  end repeat
  
return aCount
end countSpecifiedChar

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

2009/12/26 選択文字列を伏せ字に

AppleScriptエディタ上で選択中の文字列を伏せ字に置き換えます(文字数は同じですべて「X」の文字)。

/Library/Scripts/Script Editor Scriptsフォルダ以下に入れて、AppleScriptエディタのコンテクストメニューから呼び出すことを前提に作成したものです。

AppleScriptエディタ上で文字列を選択しておき……

ed1.jpg

Controlキーを押しながらマウスクリック、あるいはマウスの副(右)ボタンをクリックしてコンテクストメニューを表示して、

ed2.jpg

メニューから本Scriptを実行すると……

ed21.jpg

選択部分が伏せ字になります。

ed3.jpg

スクリプト名:選択文字列を伏せ字に
tell application “AppleScript Editor”
  try
    tell document 1
      set ab to contents of selection
      
set aCount to length of ab
      
set outStr to “”
      
repeat aCount times
        set outStr to outStr & “X”
      end repeat
      
set contents of selection to outStr
    end tell
  end try
end tell

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

2009/12/26 連続する文字(たぶんスペース)を1つにまとめる

指定文字列の中から、連続する文字(たぶんスペース)を1つにまとめるAppleScriptです。

姓と名の間にスペースが入っているんだけれど、1つとは限らない。いくつも入ってきている可能性がある……というケースに備えて、連続する文字(たぶんスペース)を1つにまとめます。

スクリプト名:連続する文字(たぶんスペース)を1つにまとめる

set aStr to "XXX XXXXX XXXXX XX XXXXXX"
set aRes to mergeMultipleChar(aStr, " ") of me

–連続する文字(たぶんスペース)を1つにまとめる
on mergeMultipleChar(aStr, aChar)
  set outStr to ""
  
set aList to characters of aStr
  
  
set successF to false
  
repeat with i in aList
    set j to contents of i
    
if j = aChar then
      if successF = false then
        set successF to true
        
set outStr to outStr & j
      else if successF = true then
        –なんにもしないよ
      end if
    else
      set successF to false
      
set outStr to outStr & j
    end if
  end repeat
  
  
return outStr
end mergeMultipleChar

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

2009/12/26 指定値を構成するペアの全パターンのリストを求める

指定値を構成するペアの全パターンのリストを求めるAppleScriptです。

……こう書いてもさっぱり意味が通じないようなので説明すると……1〜n文字の長さの文字列、たとえば人名のような文字列の姓+名のパターンを合計10文字の人名について、各文字数の出現パターンを本ルーチンで計算しておき、それぞれのパターンに該当する文字数の人名の統計をまとめる……といった用途のために作成したサブルーチンです。

実行しても、割と面白味のないリストデータが出来上がるだけですが、このような全パターンを手で確認すると骨が折れるし、プログラムリストの中にそのまま書くことがためらわれる場合もあるでしょう(例:数が多すぎる etc)。そのような場合に、このサブルーチンで組み合わせパターンのリストを生成し、プログラム内で使用するとよいかもしれません。

これでも分りづらい場合には、プログラムのパラメータと計算結果を見てください。うだうだ説明されるよりも、分りやすいと思われます。

スクリプト名:指定値を構成するペアの全パターンのリストを求める
set aRes to getPairNumListInAllPattern(10, false) of me
–> {{1, 1}, {1, 2}, {2, 1}, {1, 3}, {2, 2}, {3, 1}, {1, 4}, {2, 3}, {3, 2}, {4, 1}, {1, 5}, {2, 4}, {3, 3}, {4, 2}, {5, 1}, {1, 6}, {2, 5}, {3, 4}, {4, 3}, {5, 2}, {6, 1}, {1, 7}, {2, 6}, {3, 5}, {4, 4}, {5, 3}, {6, 2}, {7, 1}, {1, 8}, {2, 7}, {3, 6}, {4, 5}, {5, 4}, {6, 3}, {7, 2}, {8, 1}, {1, 9}, {2, 8}, {3, 7}, {4, 6}, {5, 5}, {6, 4}, {7, 3}, {8, 2}, {9, 1}}

set aRes to getPairNumListInAllPattern(10, true) of me
–> {{0, 2}, {1, 1}, {2, 0}, {0, 3}, {1, 2}, {2, 1}, {3, 0}, {0, 4}, {1, 3}, {2, 2}, {3, 1}, {4, 0}, {0, 5}, {1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}, {0, 6}, {1, 5}, {2, 4}, {3, 3}, {4, 2}, {5, 1}, {6, 0}, {0, 7}, {1, 6}, {2, 5}, {3, 4}, {4, 3}, {5, 2}, {6, 1}, {7, 0}, {0, 8}, {1, 7}, {2, 6}, {3, 5}, {4, 4}, {5, 3}, {6, 2}, {7, 1}, {8, 0}, {0, 9}, {1, 8}, {2, 7}, {3, 6}, {4, 5}, {5, 4}, {6, 3}, {7, 2}, {8, 1}, {9, 0}, {0, 10}, {1, 9}, {2, 8}, {3, 7}, {4, 6}, {5, 5}, {6, 4}, {7, 3}, {8, 2}, {9, 1}, {10, 0}}

–指定値を構成するペアの全パターンのリストを求める
on getPairNumListInAllPattern(maxNum, allowZero)
  set outList to {}
  
  
if allowZero = true then
    set startNum to 0
  else
    set startNum to 1
  end if
  
  
repeat with i from 2 to maxNum
    repeat with ii from startNum to i
      set a to ii
      
set b to i - ii
      
if allowZero = false then
        –要素の中に0が入ることを許容しない場合
        
if a is not equal to 0 and b is not equal to 0 then
          set the end of outList to {a, b}
        end if
      else
        –要素の中に0が入ることを許容する場合
        
set the end of outList to {a, b}
      end if
    end repeat
  end repeat
  
  
return outList
end getPairNumListInAllPattern

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

2009/12/25 元号変換v3

日付から元号および年を返すAppleScriptです。→ 2010/1/22 本バージョンにはバグが見つかったので、修正版を使うようにしてください。

元号の切り換えについては、年単位ではなく日単位で計算しています。

stringをdateオブジェクトにする部分は、普通に1行で組める程度の内容ですが……将来的に、文字パラメータから月や日が省略されていた場合に補う処理を入れようと考えていたものです。たとえば、2009という文字が渡されたら、それに月と日を補ってdateオブジェクトに変換する下準備を行おう、ぐらいの考えです。

日が省略されていた場合には末日を、月+日が省略されていた場合には「12/31」を補えば、世間一般で期待しているような処理になるだろうか……と考えています。

スクリプト名:元号変換v3
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
    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
    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

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

2009/12/24 dict.orgで指定の単語の意味を調べて返す

JN SoftwareのAppleScriptObjCサンプル「MenuApp ASOC」に入っていたサブルーチンを抜き出して、個人的な趣味に合うように書き換えたものです。dict.orgで指定の英単語を調べて意味を返します。

searchAndReplaceというサブルーチンは、オリジナルでは「snr」という名前であり、プログラムを読めば置換ルーチンであることは理解できるのですが、そんなアセンブラのニーモニック並みに短い名前にされては可読性が落ちまくりです(かといって、AppleScriptObjCのCocoaのClass名並みにダラダラ長くても困るのですが……なんであれは別名命名機能でも用意して短縮名称で呼ぶようなことができないんだろう ーー;)

人のソースを分析しては、使えるものはきちんと整理、分析、評価を行って、再利用しやすいように補助コードをつけて保存していますが、世界はけっこう広いので、流儀がまったく異なる流派もあってなかなか苦労することがあります。

分析時には、まず自分の流儀に合うようにプログラムを書き換えることが重要です。実にそう思います。

「my」を使う派 vs 「of me」を使う派。「toでハンドラを宣言する派」vs「onでハンドラを宣言する派」。「tellブロックで対象objectをまとめる派」vs「object’sで対象を記述する派」といった、些細な相違点があって……ASOC系のサンプルコードは「tellブロックが嫌い派」のChris Page@Appleの影響が色濃く出ており、いまひとつ読みにくいように感じます。

自分は、「tellブロックでまとめる派」なので、サンプルを書き換えてから読むようにしていますが、いつかChris Pageとは直接話をして決着を付ける必要がありそうです(ーー;。

スクリプト名:dict.orgで指定の単語の意味を調べて返す
set ASCII_13 to (ASCII character 13)
set word_to_define to "AppleScript"
set the_definitions to get_definition(word_to_define) of me
set aRes to string_to_list(the_definitions, (ASCII_13 & "." & ASCII_13)) of me

–>
(*
{"\"applescript\" foldoc \"The Free On-line Dictionary of Computing (27 SEP 03)\"
AppleScript

<language> An {object-oriented} {shell} language for the
{Macintosh}, approximately a superset of {HyperTalk}.

(1995-12-10)

."}
*)

on get_definition(word_to_define)
  try
    set ASCII_13 to (ASCII character 13)
    
set the_script to snr(("dict://dict.org/d:" & word_to_define & ":*"), " ", "%20") of me
    
–Thanks to Greg Spense for the grep code
    
set the_definitions to do shell script "curl -s " & (quoted form of the_script) & " | egrep -v ‘^220.*|^250.*|^150.*|^221.*’"
    
set the_definitions to searchAndReplace(the_definitions, "151 ", "") of me
    
set the_definitions to string_to_list(the_definitions, (ASCII_13 & "." & ASCII_13)) of me
    
return the_definitions
  on error the_err
    return the_err
  end try
end get_definition

on searchAndReplace(the_string, search_string, replace_string)
  tell (a reference to AppleScript’s text item delimiters)
    set {old_tid, contents} to {contents, search_string}
    
set {the_string, contents} to {the_string’s text items, replace_string}
    
set {the_string, contents} to {the_string as Unicode text, old_tid}
  end tell
  
return the_string
end searchAndReplace

on string_to_list(s, d)
  tell (a reference to AppleScript’s text item delimiters)
    set {o, contents} to {contents, d}
    
set {s, contents} to {s’s text items, o}
  end tell
  
return s
end string_to_list

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

2009/12/23 JN SoftwareがMenuApp ASOCのプロジェクトを公開

JN Softwareは、昔からAppleScript Studioの各種サンプルソースコードを公開しており、個人的に大変参考にしていたサイトです。同サイトで、AppleScript Objective-Cのサンプルコード「MenuApp ASOC」を公開しているのを見つけました。

メニューバーに対してメニューを付加し、メニューのサブメニューを動的に生成して、生成したメニューの各項目からハンドラを呼び出しています。

asoc1.jpg

メニューのアイコンもプログレスアイコン、アニメーションアイコンなどに変更できるようになっており、いろいろ利用価値は高そうです。これらの機能のメイン部分はObjective-Cで書かれたプログラムを呼び出すようになっており、ASOC側はコントロールを行うだけになっています。

コード内容を読んでみて、さすがのJN SoftwareもまだASOCに慣れていないためなのか、記述が冗長になっている部分が散見されます。機能を考えるとこれだけの内容で390行(コメント、空行含む)というのは、いくらなんでも長過ぎです(半分ぐらいで納めたい)。分かりやすさを優先したためか、普通ならサブルーチン化するような部分がダラダラ展開されています。

asoc2.jpg

とくにこの(↑)あたり。ここは、サブルーチン化してコンパクトにするだろー、ふつーー。……と思うのですが、ダラダラと代わり映えのしないコードが続きます。ああ、こういうのを見ると無性に効率化したくなる、、、

全体的に、使い回しを重視したようなコードが少なく、流儀の違いを感じます(とくに、User defaultの読み書きを行うあたり。ここを汎用化しないでどーする?)。この調子で大きなプログラムを作っていくのは、自分には大変危険に思えます。また、これだけ見ていると、とうてい高級言語のプログラムとは思えないほど記述効率がよくありません。

AppleScript Studioの頃は、プログラムコードを分割してload scriptで変数に読み込んで実行することで、プログラム分割+共通モジュールの使い回しを実現していました。ASOCでは、どうもこれとは違うアプローチで分割+共通モジュールの使い回しを行うことになりそうです。

ASOCに関しては上級者から初級者まで、まだ手探り状態で「汚いコード、非効率なコードのオンパレード」なのが気になります。……Apple自身がサンプルコードをそれほど出せていないところを見ると、AppleのAppleScriptチーム自身も手探り状態なのでしょうか。

2009/12/23 Photoshop CS3で指定エリアを切り出して新規保存

Photoshop CS3上で現在オープンしている画像から指定座標の範囲の内容を別画像として保存するAppleScriptです。

指定フォルダ内の画像をすべて処理するために、その1枚分の画像処理部分を試作したものです。あらかじめPhotoshopで画像をオープンしておかないとエラーになります。

ただ……Adobeのアプリケーションは、連続して処理を行わせると必ずクラッシュするもの(断言)なので、AppleScriptでクラッシュ検出処理を行うようにして、Photoshop CS3のクラッシュに備えることが重要です。

InDesignやPhotoshopなど、JavaScriptでもコントロールできるようになっていますが、動作原理上、クラッシュからのリカバリを行うにはやはりAppleScriptで外部からコントロールしたほうが便利なように思います。人がそばについて監視していないと動かせないのでは、夜間に勝手に仕事をさせておくわけにいきません。

スクリプト名:Photoshop CS3で指定エリアを切り出して新規保存
set {x1, y1} to {259, 40}
set {x2, y2} to {639, 242}

makePhotoshopSelectionRegion(x1, y1, x2, y2) of me

tell application “Adobe Photoshop CS3″
  activate
  
copy
  
set newDoc to make new document with properties {height:(y2 - y1), width:(x2 - x1)}
  
tell newDoc
    paste
  end tell
end tell

set newFilePath to (choose file name) as string

tell application “Adobe Photoshop CS3″
  set myOptions to {class:JPEG save options, embed color profile:true, format options:progressive, quality:12, scans:3}
  
save newDoc in newFilePath as JPEG with options myOptions appending lowercase extension with copying
  
close document 1 saving no
end tell

–Photoshop上で指定矩形を選択する
on makePhotoshopSelectionRegion(x1, y1, x2, y2)
  tell application “Adobe Photoshop CS3″
    tell current document
      select region {{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}} combination type replaced
    end tell
  end tell
end makePhotoshopSelectionRegion

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

2009/12/23 Photoshop上で指定矩形を選択する

Photoshop CS3上で現在オープンしている画像に対して、指定矩形座標を選択するAppleScriptです。

選択範囲を取得するのは簡単なのに、選択範囲を作成するのにはけっこう骨が折れました。Photoshp CS3のAppleScript用語辞書を調べ、Adobeが配布しているPhotoshopのScripting Guide(情報量が多い割に、サンプルScriptがイケてないのは何故?)を調べ、AppleScript Users MLの過去ログを調べ……意外と、欲しい情報というのは転がっていないもので……。

selectコマンドでパラメータを指定するというのは意外でした。さらに、座標の指定方法が四隅すべての座標を必要とするとは……。

スクリプト名:Photoshop上で指定矩形を選択する

makePhotoshopSelectionRegion(259, 36, 639, 242) of me

–Photoshop上で指定矩形を選択する
on makePhotoshopSelectionRegion(x1, y1, x2, y2)
  tell application “Adobe Photoshop CS3″
    tell current document
      select region {{x1, y1}, {x2, y1}, {x2, y2}, {x1, y2}} combination type replaced
    end tell
  end tell
end makePhotoshopSelectionRegion

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

2009/12/23 Photoshop CS3から画像の選択範囲を取得する

Photoshop CS3で画像の選択範囲の座標を取得するAppleScriptです。

ps1.jpg

大量の画像から、同じ箇所を切り取って別フォルダに別名で保存したいような場合に、最初にサンプル用の画像を1枚オープンしておいて、その画像上で選択範囲を作成しておき、AppleScriptを実行すると選択範囲の座標情報を学習。指定フォルダの画像をオープンして、最初に指定した選択範囲を切り出して別のフォルダに保存する(以下、繰り返し)……といった処理のための下調べのために作成してみました。

selectionのboundsを取得すれば選択範囲は分るのですが、何も選択していなかった場合にはこの動作を行うとエラーになってしまうため、try文でエラートラップを仕掛けておき、エラー時には何も選択されていないものと見なし、{0,0,0,0}を返します。

ps21.jpg

スクリプト名:Photoshop CS3から画像の選択範囲を取得する
tell application “Adobe Photoshop CS3″
  tell document 1
    set aSel to selection
    
try
      set {x1, y1, x2, y2} to bounds of selection
    on error
      set {x1, y1, x2, y2} to {0, 0, 0, 0}
    end try
  end tell
end tell

–> {209.0, 96.0, 777.0, 312.0}

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

2009/12/22 Excel選択範囲から各種文字列長のデータをピックアップ v6

Excel上の選択範囲から、各種文字列長のデータをピックアップするAppleScriptです。

Excel上に日本人の名前が姓と名の間に半角スペースが入った状態で並んでいたとして……諸般の都合でこれを姓1文字+名1文字のパターン、姓1文字+名2文字のパターンなど……さまざまな文字長に場合分けして、それぞれの文字長に最初に合致したデータをピックアップする、というものです。

excel1.jpg

Excelからデータを取得してはいますが、実際にはリストに突っ込んでゴリゴリ回しています。処理速度の向上のためにa reference toで高速化を行っており、これによって10倍以上は高速化できています(多くて数百件ぐらいのデータを処理していたので、割とすぐに結果が返ってきます@Core 2 Duo 2.4GHz)。

これも、最初は手作業で行っていたのですが、「そのぐらい、プログラムで処理すれば一瞬だ!」とブチ切れて、すぐに最初の試作品を作成。運用しているうちに、どんどん改良を加えて、半日もたたないうちにここまで機能が追加されました。

スクリプト名:Excel選択範囲から各種文字列長のデータをピックアップ v6
set procList to {{1, 1}, {1, 2}, {2, 1}, {2, 2}, {1, 3}, {3, 1}, {2, 3}, {3, 2}, {3, 3}}
global gaList, gaList_r
global aSelection, aSelection_r
global gaaList, gaaList_r

tell application “Microsoft Excel”
  set aSelection to formula of selection
end tell
set aSelection_r to a reference to aSelection

set gaList to {}
set gaList_r to a reference to gaList

set dameList to {}

repeat with i in aSelection_r
  set j to contents of first item of i
  
set j to repChar(j, “ ”, ” “) of me –全角スペースを半角に置換しておく
  
  
if j ends with ” “ then
    set j to deleteBackSPC(j) of me
  end if
  
  
considering hyphens
    considering white space
      set j0 to offset of ” “ in j
      
if j0 is not equal to 0 then
        set j1 to text 1 thru (j0 - 1) of j
        
        
set revJ to (reverse of (characters of j)) as string
        
set j00 to offset of ” “ in revJ
        
set j2 to text ((length of j) - j00 + 2) thru -1 of j
      else
        set j0 to offset of “ ” in j
        
if j0 is not equal to 0 then
          set j1 to text 1 thru (j0 - 1) of j
          
          
set revJ to (reverse of (characters of j)) as string
          
set j00 to offset of “ ” in revJ
          
set j2 to text ((length of j) - j00 + 2) thru -1 of j
        else
          set the end of dameList to j
        end if
      end if
    end considering
  end considering
  
  
set the end of gaList_r to {j, j1, j2, {length of j1, length of j2}}
end repeat

if dameList is not equal to {} then
  choose from list dameList with prompt “だめだった名前のリスト”
end if

set gaaList to {}
set gaaList_r to a reference to gaaList
repeat with i in procList
  set j to contents of i
  
set tmpList to {}
  
  
repeat with ii in gaList_r
    set jj to contents of item 4 of ii
    
set jj2 to contents of ii
    
if jj = j then
      set the end of tmpList to jj2
      
exit repeat
    end if
  end repeat
  
  
set the end of gaaList_r to {j, tmpList}
end repeat

gaaList
–> {{{1, 1}, {{”ぴ よ”, “ぴ”, “よ”, {1, 1}}}}, {{1, 2}, {{”ぴ よ子”, “ぴ”, “よ子”, {1, 2}}}}, {{2, 1}, {{”ぴよ こ”, “ぴよ”, “こ”, {2, 1}}}}, {{2, 2}, {{”ぴよ まる”, “ぴよ”, “まる”, {2, 2}}}}, {{1, 3}, {{”ぴ よまる”, “ぴ”, “よまる”, {1, 3}}}}, {{3, 1}, {{”ぴよま る”, “ぴよま”, “る”, {3, 1}}}}, {{2, 3}, {{”ぴよ まるお”, “ぴよ”, “まるお”, {2, 3}}}}, {{3, 2}, {{”ぴよま るお”, “ぴよま”, “るお”, {3, 2}}}}, {{3, 3}, {{”ぴよま るるこ”, “ぴよま”, “るるこ”, {3, 3}}}}}

on deleteBackSPC(a)
  set aCount to 1
  
considering hyphens
    considering white space
      
      
set aList to reverse of characters of a
      
repeat with i in aList
        set j to contents of i
        
if j is not in {” “, “  ”, “ ”} then
          exit repeat
        end if
        
        
set aCount to aCount + 1
        
      end repeat
      
    end considering
  end considering
  
  
set aa to text 1 thru ((length of a) - aCount + 1) of a
  
return aa
  
end deleteBackSPC

–文字置換ルーチン
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

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

2009/12/22 指定フォルダの中のフォルダをオープン

Finder上で選択しているフォルダの中に入っているフォルダをオープンします。

Script Menuに入れて使っています。正確にいえば、選択中のフォルダの1階層下にあるフォルダをすべてオープンします。「指定フォルダ中のファイルを消してフォルダだけ残す」AppleScriptで処理をしたフォルダに対して、再度Windowを並べるために実行するものです。

スクリプト名:指定フォルダの中のフォルダをオープン
tell application "Finder"
  set a to selection
  
set aa to first item of a
  
set aaa to aa as alias
  
  
tell folder aaa
    tell every folder to open
  end tell
end tell

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

2009/12/22 指定フォルダ中のファイルを消してフォルダだけ残す

Finder上で選択中のフォルダの中に入っているものの中から、フォルダだけ残してファイルを消去するAppleScriptです。

単純作業を行っていたときに、あまりに数が多かったのでその場で組んで使いました。Script Menuに入れて使うようにしています。Finderのdeleteコマンドで、1つのファイルを対象にするのではなく、ファイルを入れたリスト型変数を対象にすることで、一気にファイルのdelete(ゴミ箱に移動)することができます。

何種類かのデータの画面のスナップショットを撮っていて、それぞれ別のフォルダに手で移動させていました。何種類かに分ける必要があったので、複数のWindowをひらいて作業していました。ファイルの移動作業をしやすくするために、FinderのWindowをきれいにならべてファイル移動。

……これをいくつものデータに対して行っていたのですが、Windowの位置とかフォルダ名などはそのまま使い回したかったので、一度作業したフォルダ(=Window)をまるごとコピーして、中に入っている画面キャプチャファイル「だけを」消去できると都合がよかったのです。しかも、指定階層以下のすべての階層にあるやつを。

そこで、本Scriptを作成して作業を手っ取り早く終わらせました。日常的な作業の効率化を行うためには、その不毛さ加減を体感するために一度自分の手でやってみることが重要だと思います。同じ単純作業ばかりやっていると頭痛がしてくるので、すぐにAppleScriptで自動化したくなること請け合いです。

スクリプト名:指定フォルダ中のファイルを消してフォルダだけ残す
tell application “Finder”
  set a to selection
  
set aa to first item of a
  
set aaa to aa as alias
  
set aList to entire contents of aaa as alias list
  
  
set deleteList to {}
  
repeat with i in aList
    set j to contents of i
    
set aKind to kind of j
    
if aKind is not equal to “フォルダ” then –change here on another language environment. This means “folder”
      set the end of deleteList to j
    end if
  end repeat
  
  
if deleteList is not equal to {} then
    delete deleteList –これで一括消去
  end if
end tell

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

2009/12/19 TEXT/RTF/HTMLから本文を取り出す

指定したTEXTファイル、RTF、HTMLから本文を取り出すAppleScriptです。

AppleScript Users MLに流れていたScriptで、これはなかなかいいアイデアだと思います。とくに、RTFから本文部分を取り出すのは面倒だと考えていたので、テキストエディットにopenさせて内容を取り出させるというのは盲点でした。

こんな、いろいろ書式情報が付加されたRTF(Ritch Text Format)ファイルから内容を取り出せます。
rtfscreen.jpeg

これだけ手軽に取り出せるのはいいですね。ただ、肝心のTEXTファイルについては……テキストエディットでそのまま(文字エンコーディングを指定しないで)オープンできるケースとできないケースがあるため、実は何も考えないでオープンできるのはRTF/RTFDぐらいではないかと思えなくもありません。

処理前にファイルタイプを調べ、RTF/RTFDであれば本ルーチンで内容を取り出す、ぐらい慎重に処理すべきでしょう。

スクリプト名:テキスト、RTF、HTMLから内容を取り出す
set theFile to choose file with prompt “Select a text file:”
set aRes to getContentsFromTXT_PDF_HTML(theFile) of me
–>
(*
“RTF
のファイルだよーん。

*)

–選択したテキスト、RTF、HTMLから内容を取り出す
on getContentsFromTXT_PDF_HTML(theFile)
  tell application “TextEdit”
    set myDoc to open theFile
    
set theFileContents to the text of myDoc
    
close myDoc
  end tell
  
return theFileContents
end getContentsFromTXT_PDF_HTML

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

2009/12/18 Web共有を開始する

Web共有を開始するAppleScriptです。

「システム環境設定」の「共有」から設定をいじくれる「Web共有」。実体は単なるApatchだったりするわけですが、これをAppleScriptからオンにする方法を探していました。これがオンにできると便利な場合があったので、強制的にオンにできないかと調べていました。

GUI Scriptingでシステム環境設定をいじくる方法もないことはないのですが、いまひとつ「上品な」「洗練された」手段といえず、追い込まれるまで使いたくない方法です(こっそりやりたい)。

しかし、世界広しといえどもWeb共有をオンにする方法はなかなか見つからず、仕方がないからGUI Scriptingを使うしかないだろうか……などとあきらめかけていたのですが……

ふと、冒頭に述べたようにWeb共有の正体が「単なるApatch」であることを思い出し、「じゃあApatchをただ立ち上げればいいんじゃないの?」……試してみると、オプションも何も付けずにTerminal上でApatchを起動しようとしてもダメでした(権限がないと言われる)が、Apatchをスタートさせる方法について調べてみたところ、あっさりコマンドが見つかりました(Web共有をオンにする方法はまるっきり見つからなかったのに)。

さらに、これをコマンドラインから実行すると……システム環境設定にも「Web共有がオンになった」と認識されるなどいいことづくめです。

実行前:
webshare1.jpg

実行後:
webshare2.jpg

パスワードの入力は求められてしまいますが、なかなか「洗練された手口」だと思います。

スクリプト名:Web共有を開始する
do shell script “/usr/sbin/apachectl start” with administrator privileges –実行時にパスワードを聞かれる

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

2009/12/17 Mac OS X 10.6上でAppleScriptの実行が遅くなる原因はFinder?

Mac OS X 10.6.2上で、フォルダ階層をMindjet Mindmanager上にプロットしていたら、途中から極端に処理が遅くなり……耐えられないほどの遅さになりました。

Mail.appからスレッドを読み取ってMindjet MindManager上にプロットする分には、スピーディーに処理が行われます。Mindjet MindManagerのせいではなさそうです。

よくよく調べてみると、Finderから情報を取得しようとすると……次第にスピードが遅くなるように感じられました。Finderを強制終了させると元のスピードには戻るのですが、10.6のFinderはCocoaで書き直されたものであり、従来とは異なる処理を行っているはずで……そうした構造に起因するものなのか、それとも単なるバグの類いのものなのか……

Finderの速度低下に注目しつつ、さらなる観察を行いたいところです。

2009/12/16 Folder構造をMindMapに v1

選択したフォルダ以下のディレクトリ構造をMindjet MindManager上にプロットするAppleScriptです。

フォルダのみに着目し、ファイルは無視します。かーーなり適当ですが、再帰で処理しています。

Finder上でFolderにつけたラベルの色をMindMap上に再現します。

Betaが登場したMindjet MindManager 8 for Mac上でも動作確認しています。MindManagerのAppleScriptの情報源は、Googleで検索してこのBlogが最初に出てくる程度で、あまりまとまったものが存在していないといった印象がありますが、AppleScript系の機能が足りないといったことはなく、(私は)不満なく使えています。

Finder上でつけたラベルが……

mmap2.jpg

MindMap上に反映されます。

mmap1.jpg

2010年初頭にリリースされるというMindjet MindManager 8では、MindMapをFlash形式で書き出すことができるようになっており、「AppleScriptでMindMapを作成してFlashとして書き出してサイトにアップロードしてURLをメールでお知らせする」といったフローも簡単に作れそうで楽しみです。

→ マインドマップのFlash書き出し例

スクリプト名:Folder構造をMindMapに v1
–Finder上のラベルの色をMindjet MindManager上で再現するためのリスト
property labelColor : {“”, {65535, 32896, 0}, {65535, 40756, 42034}, {65535, 65535, 0}, {26214, 65535, 65535}, {48464, 44388, 65535}, {0, 65535, 0}, {50489, 50489, 50489}}
global curTopicID

set origFol to choose folder
tell application “Finder”
  set folName to name of origFol
end tell

–新規ドキュメントを作成
tell application “Mindjet MindManager”
  set aDoc to make new document
  
set rootTopicID to the id of central topic of document 1
  
tell document 1
    tell topic id rootTopicID
      set title to folName
    end tell
  end tell
  
  
set curTopicID to rootTopicID
end tell

–指定フォルダ内の第1階層を処理
tell application “Finder”
  tell folder origFol
    set folList to every folder
    
if folList is not equal to {} then
      execFolder(folList, rootTopicID) of me
    end if
    
    
(*
    set fileList to every file
    if fileList is not equal to {} then
      execFile(fileList) of me
    end if
    *)

  end tell
end tell

–指定フォルダ内のフォルダを処理
on execFolder(aFol, aTopicID)
  repeat with i in aFol
    tell application “Finder”
      set j to i as alias
      
      
tell folder j
        set aName to name
        
set aLabel to label index
        
set aLabel to item (aLabel + 1) of labelColor
        
        
set retID to addResultMainNode(aTopicID, aName as string, “”, aLabel, false) of me
        
        
set folList to every folder
        
execFolder(folList, retID) of me
      end tell
    end tell
  end repeat
end execFolder

(*
–指定フォルダ内のファイルを処理
on execFile(aFol)
  
end execFile
*)

–IDで指定したノードの下に子ノードを作成する
on addResultMainNode(topicID, aTitle, aBody, aColor, aBoundaryF)
  tell application “Mindjet MindManager”
    – 指定のトピック名称で新規トピックを作成
    
tell document 1
      set resTopic to make new subtopic for topic id topicID with properties {title:aTitle}
      
set notes of resTopic to aBody
      
      
–囲みをつけるかどうか
      
if aBoundaryF = true then
        make new boundary for resTopic
      end if
      
      
–指定がある場合にはfill colorを変更
      
if aColor is not equal to “” then
        set fill color of resTopic to aColor
      end if
    end tell
    
    
return id of resTopic –作成したトピックのIDを返す
  end tell
end addResultMainNode

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

2009/12/15 最前面のアプリケーションに対するAppleScriptを新規作成 v3

最前面のアプリケーションに対するAppleScriptを新規作成し、同アプリケーションの用語辞書をAppleScriptエディタでオープンするAppleScriptです。Script Menuに入れて使います。

as2.jpg

/Library/Scriptsフォルダ内に入れて使うことを前提にしています。ここに入れたScriptは、どのアプリケーションが前面に表示されていてもメニューに表示されます。

本Scriptを実行すると、最前面のアプリケーションに対するtellブロックのAppleScriptが新規作成され、同アプリケーションの用語辞書がAppleScriptエディタで表示され、AppleScriptエディタが一番前に表示されます。

as1.jpg

ただし、スクリプタブル「ではない」アプリケーションに対して実行したときのエラートラップが有効に機能しておらず、AppleScriptエディタでエラーがキャッチされて実行が止まってしまいます。こうなると、AppleScriptエディタを強制終了する必要があるため、気をつけてください。

System EventsとFinderを使って最前面のアプリケーションがAppleScriptに対応しているかどうかチェックしてから処理……というパターンも書いてみたものの、Scriptableでないクセに「has scripting terminology」がtrue(System Events/Finder)だったり、「accepts high level events」がtrue(Finder)だったりするアプリケーションがあった(iThink 9.1.3 Trial)ので、搭載を見送った経緯があります。

スクリプト名:最前面のアプリケーションに対するAppleScriptを新規作成 v3
tell application “System Events”
  set appList to every process whose frontmost is true
  
set anApp to first item of appList
  
set aName to name of first item of appList
  
set aFile to file of anApp
end tell

try
  tell application id “com.apple.ScriptEditor2″
    open aFile
  end tell
on error aMes
  –display dialog “アプリケーション「” & aMes & “」のAppleScript用語辞書のオープンを試みましたが、以下の理由により失敗しました:” & return & aMes
  
return
end try

set aCon to “tell application “ & (ASCII character 34) & aName & (ASCII character 34) & return & return & “end tell” & return

tell application id “com.apple.ScriptEditor2″
  set aNewDoc to make new document
  
tell document 1
    set contents to aCon
    
try
      compile
    end try
  end tell
  
activate
end tell

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

2009/12/15 Googleの未公開APIで英文テキスト読み上げ

Googleの未公開APIを呼び出して英文テキストの読み上げ(Text To Speech)を行うAppleScriptです。

# Mac OS X 10.7では、URL Access Scriptingが廃止になったので、本Scriptはそのままでは動きません

本当は「open location」文でURL+パラメータを指定すればWebブラウザで音声が再生されてそれでオシマイ、という程度の処理なのですが……それではあまりに芸がないので、ローカルにGoogleが吐いたMP3データをダウンロードして、QuickTime Player Xで再生(10.5や10.4を使っている人は、この部分だけ書き換えてください)するようにしてみました。

いつまでもこのAPIが使えるかの保証はありません(使えなくなっていたら教えてください)。ただ……このまま行くと「多言語Text To Speech」の機能に育っていきそうな予感が……。

別に、Mac OS Xではsayコマンドで英文テキスト読み上げの機能があるわけですが、Web上のサービスを使えるということは、同時に並列処理でいくつもリクエストを送って処理できるというわけで……長い文章も並列リクエスト&ダウンロードだけで読み上げデータが作れる可能性があるわけです。

スクリプト名:Googleの未公開APIで英文テキスト読み上げ
–Googleの未公開APIで英文テキスト読み上げ

–参照:
–http://jp.techcrunch.com/archives/20091214the-unofficial-google-text-to-speech-api/

–文章をダイアログから入力
set aMes to “I’m lerning AppleScript very hard”
set aMes to (text returned of (display dialog “Googleにしゃべらせる英語の文章を入力(100文字以内)” default answer aMes))

–文章の長さチェック。文字種類チェックはしていないので自己責任で
if length of aMes > 100 then
  display dialog “100文字以内で入力してください。” buttons {“OK”} default button 1 with icon 1
  
return
end if

–パスをstringでcastしたあとにunicodeにcastしているのはMac OS X 10.4までの仕様。10.5以降では必要ない
set aPath to (path to temporary items from user domain as text)

–URL Access Scriptingで生成できるファイルの名称が32文字までなので、ちょっとuuidgenの結果を短くして使う
set dPath to aPath & (text 1 thru 20 of (do shell script “/usr/bin/uuidgen”)) & “.mp3″ as Unicode text

set baseURL to “http://translate.google.com/translate_tts?q=”
set aParam to repChar(aMes, ” “, “+”) of me

set aURL to baseURL & aParam

–Googleにリクエストを投げて、返ってきたMP3ファイルをローカルに保存する
try
  with timeout of 30 seconds
    tell application “URL Access Scripting”
      activate
      
download aURL to file dPath with progress
    end tell
  end timeout
on error
  display dialog “Download Error” buttons {“OK”} default button 1 with icon 2
  
return
end try

–ダウンロードした音声ファイルをオープンして再生
tell application id “com.apple.QuickTimePlayerX”
  open file dPath
  
tell document 1
    play
  end tell
end tell

–文字置換ルーチン
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

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

2009/12/14 選択中のMailのSubjectの変化を吸収するための試作

Mail.appで選択中のメールをもとに、同一スレッド内のSubjectの変化を吸収して、最低限の変化のみを検出するようにするための試作AppleScriptです。

目下、Mail.app上で選択中のメールをもとにスレッドの参照関係図をMindjet MindManager上にプロットするAppleScriptを開発・実戦投入しており、入り組んだメールのフローをビジュアライズできています。

mind1.jpg

海外のMLに対しては非常に有効に使えているのですが、日本のMLの運用方法が海外のものと異なる場合が多いため、日本語のMLの情報をプロットしようとすると、Subject変更点がうまく検出できず、1通ずつSubjectが変更されているものとして検出されていたりします。

これを防ぐために、日本国内のMLの特殊な運用がどこなのか考えてみたところ……すべてのケースでそうとはいえないものの、

・Subject内のMLの識別子にシリアル番号を入れており、この部分が1通1通変化する

という現象に対処してあげればよいということが分りました。日本語Subjectの末尾が化けるといったMLに遭遇したこともありましたが、さすがに最近はそういうこともなくなってきたようで……。

そこで、そうした日本国内MLのSubjectの特性に合わせてSubjectの変化を吸収するような処理を試作してみました。実際に、現在使っているプログラムに組み合わせて使ってみようと思います。

スクリプト名:選択中のMailのSubjectの変化を吸収するための試作
–選択中のMailのSubjectの変化を吸収するための試作
tell application "Mail"
  set aSel to selection
  
set sbjList to {}
  
repeat with i in aSel
    set the end of sbjList to subject of i
  end repeat
end tell
sbjList
–> (*[sampleml:00019] ○○の話 , [sampleml:00021] Re: ○○の話 , [sampleml:00020] Re: ○○の話 , [sampleml:00024] Re: ○○の話 *)

set newList to {}
repeat with i in sbjList
  set j to contents of i
  
if "[" is in j and "]" is in j then
    set j1 to offset of "]" in j
    
set j2 to text (j1 + 1) thru -1 of j
    
set j2 to repChar(j2, " Re: ", "") of me
    
set j2 to repChar(j2, " ", "") of me
    
set the end of newList to j2
  end if
end repeat
newList
–>{"○○の話", "○○の話", "○○の話", "○○の話"}

–文字置換ルーチン
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

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

2009/12/14 オブジェクトの複数同時指定

AppleScriptにおける高速化手法のうちの1つに、「複数オブジェクト同時アクセス」と呼ぶべきものがあります。

たとえば、Finderで指定フォルダ中の特定のアプリケーション書類を取得して、別のフォルダに移動したいといった場合に、取得した書類パスのリストをもとにループさせるとそれなりに時間がかかりますが、実はFinderのファイル移動命令のパラメータとして「リストに入ったパスそのもの」が使えるので、複数のファイルを一度に移動させたりできます。

同様に、Mail.appの特定のメッセージを別のメールボックス(フォルダ)に移動させる際に、「複数のメッセージが入ったリスト」を指定でき、一度の移動命令で複数のメールを移動させることができます。

AppleScript Studioでは、Window上のGUI部品……たとえば400個ボタンに対して同時にvisibleを指定したり、titleを指定したりできます(AppleScriptObjCでは……まだ、そこまでテストできていません)。

こうした複数オブジェクト同時アクセスを利用すれば、AppleScriptで格段に高速な処理を実現できます。

どのアプリケーションで複数オブジェクト同時指定ができるのかについては、とりあえず思いつくものはこの程度です。実際に試してみないと使えるかどうか分らない……記述できるかどうかは、あくまでアプリケーション側の対応度次第。AppleScriptが難解だとよく言われますが、実際にはアプリケーション側のScripting対応機能がまちまちであるために、一貫性がない言語と感じられてしまうことが理由になっていることでしょう。そして、「アプリケーションごとにScripting対応度がまちまちだ」という傾向はScripting言語がAppleScript以外のJavaScriptやRubyであっても大差はないものと思われます。

2009/12/12 選択したアプリケーションのInfo.plistを読んで、実行バイナリの名称を取得する

選択したアプリケーションファイルのバンドル内のInfo.plistを読んで、実行バイナリの名称を取得するAppleScriptです。

スクリプト名:選択したアプリケーションのInfo.plistを読んで、実行バイナリの名称を取得する
(*
選択したアプリケーションのInfo.plistを読んで、
実行バイナリの名称を取得する
*)

set a to choose file
set aP to POSIX path of a
set infoPath to aP & "Contents/Info.plist"

tell application "System Events"
  set infoplistFile to contents of property list file infoPath
  
set anExecutable to value of property list item "CFBundleExecutable" of infoplistFile
end tell

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

2009/12/12 PlistファイルをParseして返す

PlistファイルをParseして返すAppleScriptです。

各アプリケーションのプロパティ内容は~/Library/Preferencesフォルダにplistファイルで書かれることが多く、その内容を読めると役に立つこと「も」あります。

どちらかといえば、既存のアプリケーションについてはshellのdefaults readコマンドで読むことが多く、System Eventsに命令を送ってプロパティの内容を読み取るのは、自分で作ったAppleScript Studioアプリケーションの場合が多いように感じます。

スクリプト名:PlistファイルをParseして返す
set anAlias to choose file
set aRes to parseAndRetPlistEntryFromFile(anAlias) of me

–PlistファイルをParseして返す
on parseAndRetPlistEntryFromFile(aFile)
  set f to POSIX path of aFile
  
set res to {}
  
try
    tell application “System Events”
      set plif to property list file f
      
set pitm to every property list item of plif
      
repeat with p in pitm
        set end of res to {name, value} of p
      end repeat
    end tell
  on error
    return {}
  end try
  
return res
end parseAndRetPlistEntryFromFile

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

2009/12/12 クリップボードに入っている情報の改行を外してクリップボードに書き戻す

クリップボードに入っている内容をテキストと見なして、改行コードを除去し、クリップボードに内容を書き戻すAppleScriptです。

クリップボードに入っていたのがテキストでなかった場合には、多分エラーになるので……これに備えてエラートラップを仕掛けています。

スクリプト名:クリップボードに入っている情報の改行を外してクリップボードに書き戻す
try
  set a to the clipboard
  
set b to a as string
  
set c to repChar(b, ASCII character 10, "") of me
  
set c to repChar(c, ASCII character 13, "") of me
  
set the clipboard to c
end try

–文字置換ルーチン
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

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

2009/12/12 画像のヒストグラムの中央値を求める

Photoshop/Photoshop Elementsで画像のヒストグラムを求めた結果から、その中央値を求めるAppleScriptです。

ヒストグラムという言葉の定義を考えると、「度数分布を表した柱状グラフ」ということになるわけですが……このままだとピンと来ないので、Photoshop上のヒストグラムについて具体的にいえば、「画像の明度を256段階に分けて、それぞれの明度に該当するドット数をカウントしてグラフ化したもの」です。

その中央値を求めるということは、画像中の明度分布の平均値がどのあたりにあるか算出するための試みを行うというわけで、画像のレベル自動変更といった処理をにらんで作ってみました。

ここで用いたサンプル画像だと、中央値がレベル140ということなので、

hist1.jpg

こんな感じです。なんとなく、こんな感じかといわれればこんな感じなのですが、この中央値をもってサンプル画像が明るいか暗いかは判断つきかねるところです。

hist2.jpg

実際、この中央値が140の画像はちょっと暗めのものだったので……中央値を求めてもこの用途には使えなさそうです。何か別の用途には使えるかもしれません。

画像の自動レベル補正については、PhotoshopのAppleScript用語辞書にadjustコマンドが用意されており、

スクリプト名:Photoshop CS3で画像の明度レベル自動補正
tell application “Adobe Photoshop CS3″
  tell document 1
    tell art layer 1
      adjust using automatic levels
    end tell
  end tell
end tell

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

このぐらいでさくっと実行できるのですが、Photoshopの自動補正程度ではぜんぜん役立ちません。

別途、レベル自動補正のための処理を考え、そちらで実現した次第です。

スクリプト名:ヒストグラムの中央値を求める
set bList to {8, 110, 232, 314, 388, 401, 516, 730, 832, 737, 667, 832, 1097, 1478, 1668, 1454, 872, 670, 505, 444, 437, 395, 318, 340, 335, 400, 445, 498, 527, 570, 620, 681, 713, 766, 801, 835, 856, 972, 1139, 1311, 1412, 1153, 969, 955, 909, 839, 859, 975, 1003, 1087, 1001, 948, 911, 1018, 1120, 1067, 1131, 1092, 1139, 1119, 1036, 1037, 970, 980, 916, 889, 807, 831, 809, 822, 817, 799, 882, 828, 932, 848, 910, 1018, 1112, 1223, 1449, 1646, 1891, 1925, 2141, 2978, 4092, 4816, 4368, 3673, 2876, 1964, 1610, 1420, 1478, 1386, 1328, 1207, 1247, 1298, 1261, 1250, 1354, 1475, 1361, 1409, 1766, 2476, 3686, 5015, 6218, 6639, 6322, 6007, 6414, 7525, 8596, 9065, 9560, 11121, 11976, 11515, 11668, 12823, 14802, 16467, 18105, 21367, 25910, 27244, 25368, 24587, 24538, 23670, 21556, 21098, 24165, 26423, 24957, 22997, 23679, 24743, 23797, 21897, 22037, 22317, 21025, 18623, 18966, 19382, 18671, 16789, 15486, 16482, 17025, 15849, 14893, 16760, 18356, 17653, 14952, 14055, 14657, 14746, 14015, 14080, 14864, 15049, 13967, 11706, 11014, 9662, 8178, 6149, 5889, 5296, 4059, 3136, 2813, 2136, 1236, 711, 581, 510, 468, 436, 457, 388, 435, 355, 385, 398, 373, 361, 333, 372, 355, 338, 287, 296, 295, 327, 270, 296, 262, 245, 240, 250, 210, 231, 222, 245, 230, 243, 249, 223, 204, 232, 225, 257, 248, 228, 260, 252, 219, 255, 283, 302, 274, 270, 343, 353, 325, 338, 401, 435, 509, 503, 497, 576, 581, 553, 476, 345, 224, 110, 59, 26, 13, 15, 7, 0, 0, 0, 0, 0}

–リスト要素の合計を計算する
set iCount to 0
repeat with i in bList
  set iCount to iCount + i
end repeat
set halfVal to iCount div 2 –画像の全画素のドット数で数えて中央の要素を計算(何個目のドットが中央か)

–ドット個数の中央値を超える要素をループで検出する
set iiCount to 0
set iiCounter to 1
repeat with i in bList
  set iiCount to iiCount + i
  
if iiCount is not less than halfVal then
    exit repeat
  end if
  
set iiCounter to iiCounter + 1
end repeat

iiCounter –暗いドットから明るいドットまですべて順番に並べて、個数的に真ん中のものの明るさが求められる
–> 140

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

2009/12/08 Mindjet MindManager上のデータをリストに展開してCSV書き出し

Mindjet MindManager上に展開したマインドマップを、選択中のトピック以下を対象に、いい感じにトピックのレベルを反映してCSV書き出しするAppleScriptです。

mj1.jpg

リスト上でトピックの配置を行い、Excelのワークシート上に……そのまま持って行くのは、不可能ではないにせよかったるい(r1c1形式とA1形式の変換サブルーチン(しかも巨大)を投入する必要がある)ため、以前にリストをCSV書き出しするサブルーチンを作ってあったので、CSV書き出ししておくことにしました。

mj2.jpg

書き出したCSVのファイルをExcelでオープンすれば、めんどくさい処理なしにExcelにマインドマップの内容を渡せます。

いい感じでマインドマップ上にアイデアを書き込んでいたところに「そのままだと分りづらいからExcelの表にして」などと言われ、一気にテンションが落ち……ないよう作ってみたものです。

風呂上がりにテレビを見ながら、いいかげんに作ったので……a reference toによる高速化を試みているものの、ろくに高速化されていなかったりしますが、目くじらを立てるほど遅くないため放っておきました。

スクリプト名:Mindjet MindManager上のデータをリストに展開してCSV書き出し
global gList, gList_r, levelList

set gList to {}
set gList_r to a reference to gList
set levelList to {}

tell application “Mindjet MindManager”
  tell document 1
    set aSel to selection
  end tell
  
set aaSel to first item of aSel
  
addMMSubtreeToGlobalVal(aaSel) of me
end tell

–行(row)の最大値、列の最大値(levelList)を取得する
set rowNum to length of gList_r
set colMax to maximumFromList(levelList) of me

–カラのリストを作成する
set blankList to makeBlankList(rowNum, colMax, “”) of me

–リストを作成する
set tmpCol to 1
set tmpRow to 1

set prevCol to 0
set prevRow to 0

set prevLevel to 0

repeat with i in gList_r
  set {aTitle, aLevel} to i
  
  
if aLevel < prevCol then
    set tmpRow to tmpRow + 1
  else if aLevel = prevCol then
    set tmpRow to tmpRow + 1
  else
    
  end if
  
  
–set item tmpRow of item aLevel of blankList to aTitle
  
set item aLevel of item tmpRow of blankList to aTitle
  
  
set prevCol to aLevel
  
set prevRow to tmpRow
  
end repeat

set aNewFile to choose file name
saveAsCSV(blankList, aNewFile) of me

–指定の大きさでカラのリストを作成する
on makeBlankList(rowMax, colMax, anItem)
  set allData to {}
  
  
repeat rowMax times
    set aRow to {}
    
repeat colMax times
      set the end of aRow to anItem
    end repeat
    
set the end of allData to aRow
  end repeat
  
  
return allData
end makeBlankList

–Mindjet MindManager上のトピックを渡すと、その子トピックの情報を収集して、サブトピックを再帰で取得
–結果はGlobal変数(gList)に追記する
–別のサブルーチンを書き換えてそのままの名前で使ってしまった
on addMMSubtreeToGlobalVal(mmTopic)
  tell application “Mindjet MindManager”
    repeat with childTopic in subtopics of mmTopic –指定トピック下の子トピックを取得するのに、この書き方しかMindjet MMが受け付けてくれない
      set topicTitle to (a reference to the title of childTopic)
      
set aLevel to level of childTopic
      
set aTitle to title of childTopic
      
set the end of gList_r to {aTitle, aLevel}
      
set the end of levelList to aLevel
      
addMMSubtreeToGlobalVal(childTopic) of me
    end repeat
  end tell
end addMMSubtreeToGlobalVal

–最大値を取得する
on maximumFromList(nList)
  script o
    property NL : nList
  end script
  
  
set max 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 > max then set max to n
  end repeat
  
return max
  
end maximumFromList

–CSV書き出し
–ただし、データ内にダブルクォートが入っていた場合に備えてのサニタイズ処理は行っていない
on saveAsCSV(aList, aPath)
  set crlfChar to (ASCII character 13) & (ASCII character 10)
  
set LF to (ASCII character 10)
  
set wholeText to “”
  
repeat with i in aList
    set aLineText to “”
    
set curDelim to AppleScript’s text item delimiters
    
set AppleScript’s text item delimiters to “\”,\”"
    
set aLineList to i as text
    
set AppleScript’s text item delimiters to curDelim
    
    
set aLineText to repChar(aLineList, return, “”) of me –データの途中に改行が入っていた場合には削除する
    
set aLineText to repChar(aLineText, LF, “”) of me –データの途中に改行が入っていた場合には削除する
    
    
set wholeText to wholeText & “\”" & aLineText & “\”" & crlfChar –行ターミネータはCR+LF
  end repeat
  
  
if (aPath as string) does not end with “.csv” then
    set bPath to aPath & “.csv” as Unicode text
  else
    set bPath to aPath as Unicode text
  end if
  
  
write_to_file(wholeText, bPath, false) of me
  
end saveAsCSV

–ファイルの追記ルーチン「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

–文字置換
on repChar(origText, targChar, repChar)
  set origText to origText as string
  
set targChar to targChar as string
  
set repChar to repChar as string
  
  
set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to targChar
  
set tmpList to text items of origText
  
set AppleScript’s text item delimiters to repChar
  
set retText to tmpList as string
  
set AppleScript’s text item delimiters to curDelim
  
return retText
end repChar

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

2009/12/07 指定の大きさでカラのリストを作成する

指定のサイズで、空のリストを作成するAppleScriptです。

Excelのワークシートから値を取得した状態、もしくはそのままExcelのワークシートに値をセットできる構造の空のリストを作成します。

Excelのワークシート上にデータをセットする際に、あらかじめリスト上でデータの配置を行っておいて、Excelワークシートに一度のデータ転送で値をセット。アプリケーションとの通信量を減らすことで、処理速度を大幅に高速化することをねらっています。

スクリプト名:指定の大きさでカラのリストを作成する
set aList to makeBlankList(5, 3, "") of me
–> {{"", "", ""}, {"", "", ""}, {"", "", ""}, {"", "", ""}, {"", "", ""}}

–指定の大きさでカラのリストを作成する
on makeBlankList(rowMax, colMax, anItem)
  set allData to {}
  
  
repeat rowMax times
    set aRow to {}
    
repeat colMax times
      set the end of aRow to anItem
    end repeat
    
set the end of allData to aRow
  end repeat
  
  
return allData
end makeBlankList

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