09/02 簡単な素因数分解

簡単な素因数分解を行うAppleScriptです。

簡単な……というのは、1000までの数に処理対象を限定しているのと、あんまりしっかり検証していないので「あ、動くからいいや」ぐらいの感じでしかありません。実務でビシバシにいじめまくって、これなら大丈夫……というほど裏がとれているわけでもない、という感じです。

そもそも、なんでまた素因数分解などを作ろうと考えたかといえば、InDesign上のオブジェクトにスクリプトラベルをつける作業をしていたときに、最近こういう作業はぜんぶAppleScriptから自動で採番して実行するのですが、ラベルを付ける対象が数百個にも達し、しかもそれらが整然と並んでいるため、

 「縦に何個並んでいるか、全体の個数から勝手に判断してくれたらいいのに〜!」

などと、ふと思ってしまったからで、「じゃあ、要素数を素因数分解して、適当な候補数をchoose from listで表示してくれたらいいな〜」などと、あまり考えずに勢いだけで作りはじめたのがきっかけです(長いな)。

素因数分解などとおおげさなことを言っても、しょせんは素数で割ったリストにすぎないので、1から1000までの素数のリストを取り寄せて、割り算しながらループを回すというお気楽仕様に。思い立ってから出来上がるまで、10分ぐらい。

これで結局役に立たなくて、version 2を作る羽目になるのですが……。

スクリプト名:簡単な素因数分解
set a to 312
set b to returnDivisor(a) of divisorKit
–> {2, 2, 2, 3, 13}

–簡単な素因数分解
script divisorKit
  on returnDivisor(a)
    –1000以下の素数のリスト (prime number list under 1000)
    
set primeNum1000 to {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997}
    
    
if a > 1000 then return false –1000より大きかったらfalseをリターン(計算範囲外エラー)
    
    
if a is in primeNum1000 then
      return true –1000までの数字で、素数だった場合
    end if
    
    
–ここからメイン処理
    
set divisorList to {}
    
    
repeat while a > 0
      set {r1, r2} to returnDivisorSub(a, primeNum1000) of me
      
if r1 = false then exit repeat
      
      
set the end of divisorList to r1
      
      
set a to r2
    end repeat
    
    
return divisorList
  end returnDivisor
  
  
  
on returnDivisorSub(a, primeNumList)
    repeat with i in primeNumList
      set a1 to a div i
      
set a2 to a / i
      
if a1 = a2 then
        return {contents of i, a1}
      end if
    end repeat
    
return {false, false}
  end returnDivisorSub
end script

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

09/02 iTunes 10でAppleScript用語辞書に変更なし

9/1の発表会で紹介され、その日……のうちギリギリで? ダウンロード可能になったiTunes 10。インストールして真っ先に行うのはAppleScript用語辞書が変更されているかのチェック。大規模な変更があった場合には、さまざまなプログラムを山ほど書き直さなくてはなりません。

it10_1.jpg

it10_2.jpg

HTMLに書き出して比較してみたところ、前バージョンからとくに変更点はない(0箇所)ということが分りました。

とりあえず、辞書上では変更がないということであり、挙動が変わっていないかどうかはこれからじっくり調べることになるでしょう。

08/30 実体参照している文字列をデコードする

HTMLの中などで実体参照(Character reference)しているエンコードされた文字列をデコードするAppleScriptです。

特定用途のために作成したので、その用途にだけ役立てばいいという割り切りをして作りました。なので、すべての用途に使えるという汎用性を保証するレベルのものではありません。

また、他の言語処理系の機能を呼び出すことで、もっと楽にできたのではないか……などとは思っています。

例によって、リスト中の半角バックスラッシュが円マーク(¥)に置き換えられていますが、AppleScriptエディタにプログラム内容を転送するリンクをクリックすれば正しい内容が転送されます。

スクリプト名:実体参照している文字列をデコードする
set aStr to "\"龍馬伝\""
set bStr to trimStrFromTo(aStr, "\"", "\"") of me
set cRes to decodeCharacterReference(bStr) of me
–> "龍馬伝"

set aStr to "\"第2エア\"" –英数字などが混在しているパターンの文字列
set bStr to trimStrFromTo(aStr, "\"", "\"") of me
set cRes to decodeCharacterReference(bStr) of me
–> "第2エア"

–実体参照している文字列をデコードする
on decodeCharacterReference(aStr)
  set aList to parseByDelim(aStr, ";") of me
  
  
set newStr to ""
  
  
repeat with i in aList
    set j to i as string
    
if j contains "&#" then
      set aPos to offset of "&#" in j
      
set preChar to ""
      
if aPos is not equal to 1 then
        set preChar to text 1 thru (aPos - 1) of j
        
set jj to text (aPos + 2) thru -1 of j
      else
        set jj to j
        
      end if
      
      
set bStr to repChar(jj, "&#", "") of me
      
set cStr to string id (bStr as number)
    else
      set cStr to j
      
set preChar to ""
    end if
    
set newStr to newStr & preChar & cStr
  end repeat
  
  
if newStr contains "&" then
    set newStr to repChar(newStr, "&amp", "&") of me
    
set newStr to repChar(newStr, "&lt", "<") of me
    
set newStr to repChar(newStr, "&gt", ">") of me
    
set newStr to repChar(newStr, "&quot", "\"") of me
  end if
  
  
return newStr
end decodeCharacterReference

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

on trimStrFromTo(aStr, fromStr, toStr)
  –fromStrは前から探す
  
if fromStr is not equal to "" then
    set sPos to (offset of fromStr in aStr) + 1
  else
    set sPos to 1
  end if
  
  
–toStrは後ろから探す
  
if toStr is not equal to "" then
    set b to (reverse of characters of aStr) as string
    
set ePos to (offset of toStr in b)
    
set ePos to ((length of aStr) - ePos)
  else
    set ePos to length of aStr
  end if
  
set aRes to text sPos thru ePos of aStr
  
return aRes
end trimStrFromTo

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

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

08/29 2つのdateの差を月単位で取得する

2つのdateオブジェクトの差を年および月単位で取得するAppleScriptです。

aDateObj < bDateObj が前提条件です。

スクリプト名:2つのdateの差を月単位で取得する
set aDate to “2001/9/1″
set aDateObj to date aDate

set bDate to “2010/8/1″
set bDateObj to date bDate

set dRes to monthDiff(aDateObj, bDateObj) of me
–> {yearDiff:8, monthDiff:11}

–2つのdateの差を月単位で取得する
on monthDiff(aDateObj, bDateObj)
  set aYear to year of aDateObj
  
set aMonth to month of aDateObj as number
  
  
set bYear to year of bDateObj
  
set bMonth to month of bDateObj as number
  
  
if aMonth > bMonth then
    set dMonth to (12 - aMonth) + bMonth
    
set dYear to bYear - aYear - 1
  else
    set dMonth to (bMonth - aMonth)
    
set dYear to bYear - aYear
  end if
  
  
return {yearDiff:dYear, monthDiff:dMonth}
  
end monthDiff

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

08/24 与えられた日付の「月」が異なるかどうかチェック

与えられた2つの日付の「月」(month)が異なるかどうかチェックするAppleScriptです。

スクリプト名:与えられた日付の「月」が異なるかどうかチェック
set date1 to "2011/1/27"
set date2 to "2011/2/5"

set d1Obj to date date1
set d2Obj to date date2

set dRes to getDifferenceOfMonth(d1Obj, d2Obj) of me
–> false

–与えられた日付の「月」が異なるかどうかチェック
on getDifferenceOfMonth(d1Obj, d2Obj)
  set m1 to month of d1Obj as number
  
set m2 to month of d2Obj as number
  
return (m1 = m2)
end getDifferenceOfMonth

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

08/24 指定年月の最後のx曜日を返す

指定年月の最後の○曜日(日曜日=1、土曜日=7)を返すAppleScriptです。

スクリプト名:指定年月の最後のx曜日を返す
–2011年の12月の最後の月曜日
set lastDay to retLastWeekday_X(2011, 12, 2) of lastXdayKit
–> 26

script lastXdayKit
  –指定年月の最後のx曜日を返す
  
on retLastWeekday_X(aYear, aMonth, aWeekDayNum)
    set aLen to getMlen(aYear, aMonth) of me
    
    
set xDayList to {}
    
repeat with i from 1 to aLen
      set dStr to (aYear as string) & "/" & (aMonth as string) & "/" & (i as string)
      
set aDate to date dStr
      
set theWeekdayN to weekday of aDate as number
      
if theWeekdayN = aWeekDayNum then
        set the end of xDayList to i
      end if
    end repeat
    
    
return contents of last item of xDayList
    
  end retLastWeekday_X
  
  
  
–指定月の日数を返す
  
on getMlen(aYear, aMonth)
    set aYear to aYear as number
    
set aMonth to aMonth as number
    
    
set aDat to (aYear as text) & "/" & (aMonth as text) & "/1"
    
    
if aMonth is 12 then
      set eDat to ((aYear + 1) as text) & "/" & (1 as text) & "/1"
    else
      set eDat to ((aYear as text) & "/" & (aMonth + 1) as text) & "/1"
    end if
    
    
set eDat to date eDat
    
set eDat to eDat - 1
    
    
set mLen to day of eDat
    
return mLen
  end getMlen
end script

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

08/24 指定年月の最初のx曜日を返す

指定年月の最初の○曜日(日曜日=1、土曜日=7)を取得するAppleScriptです。

スクリプト名:指定年月の最初のx曜日を返す
—2011年1月の最初の月曜日(=2)を求める
set fRes to retFirstWeekday_X(2011, 1, 2) of firstXdayKit
–> 3
–つまり、2011/1/3が最初の月曜日

script firstXdayKit
  –指定年月の最初のx曜日を返す
  
on retFirstWeekday_X(aYear, aMonth, aWeekDayNum)
    set aLen to getMlen(aYear, aMonth) of me
    
    
repeat with i from 1 to aLen
      set dStr to (aYear as string) & "/" & (aMonth as string) & "/" & (i as string)
      
set aDate to date dStr
      
set theWeekdayN to weekday of aDate as number
      
if theWeekdayN = aWeekDayNum then
        return i
      end if
    end repeat
    
  end retFirstWeekday_X
  
  
–指定月の日数を返す
  
on getMlen(aYear, aMonth)
    set aYear to aYear as number
    
set aMonth to aMonth as number
    
    
set aDat to (aYear as text) & "/" & (aMonth as text) & "/1"
    
    
if aMonth is 12 then
      set eDat to ((aYear + 1) as text) & "/" & (1 as text) & "/1"
    else
      set eDat to ((aYear as text) & "/" & (aMonth + 1) as text) & "/1"
    end if
    
    
set eDat to date eDat
    
set eDat to eDat - 1
    
    
set mLen to day of eDat
    
return mLen
  end getMlen
end script

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

08/24 指定日がその年の何日目かを取得する

任意の指定日(どの日でも好きな日を指定)が、その年の何日目かを取得するAppleScriptです。

スクリプト名:指定日がその年の何日目かを取得する
set aYear to 2011
set tDate to "2011/1/3"
set tDateObj to date tDate

set a to retTheNumberOfDays(tDateObj) of me
–> 3

–指定日がその年の何日目かを取得する
on retTheNumberOfDays(aDate)
  set aYear to year of aDate
  
  
set sDate to (aYear as string) & "/1/1"
  
set dDiff to (aDate - (date sDate)) div days + 1 –divは整数除算
  
  
return dDiff
end retTheNumberOfDays

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

08/22 指定年_月の初日(1日)がその年の何日目かを取得する

指定年&月の初日(●月1日)がその年の何日目かを取得するAppleScriptです。

カレンダーを作成する際に、支給されたイベントのデータを実際に週カレンダーや月カレンダーに入れたりするわけですが、その際のデータの頭出し用に作成したものです。

2011年12月1日が2011年の何日目か分かれば、listに入れておいたイベントデータの何アイテム目にアクセスすればよいか簡単に分かります。

スクリプト名:指定年_月の初日(1日)がその年の何日目かを取得する
set aYear to 2011
set aMonth to 12

set a to retDayNumUntillTheFirstDayOfTheMonth(aYear, aMonth) of me
–> 335

–指定年/月の初日(1日)がその年の何日目かを取得する
on retDayNumUntillTheFirstDayOfTheMonth(aYear, aMonth)
  set fD to (aYear as string) & "/1/1"
  
set fDobj to date fD
  
  
set tD to ((aYear as string) & "/" & aMonth as string) & "/1"
  
set tDobj to date tD
  
  
set diffD to ((tDobj - fDobj) / days) + 1
  
  
return diffD as integer
end retDayNumUntillTheFirstDayOfTheMonth

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

08/22 指定年&月の初日が何曜日かを数値で取得する

指定の年&月の初日が何曜日かを数値で取得するAppleScriptです。

たとえば、2011年1月を指定すると7(土曜日)が返ってきます。

2011cal.jpg

スクリプト名:指定年&月の初日が何曜日かを数値で取得する
set aYear to 2011
set aMonth to 1

getWeekdayNumOfFirstDay(aYear, aMonth) of me
–> 7

–指定年&月の初日が何曜日かを数値で取得する
on getWeekdayNumOfFirstDay(aYear, aMonth)
  set fD to ((aYear as string) & "/" & aMonth as string) & "/1"
  
set fDobj to date fD
  
set fNum to weekday of fDobj as number
  
return fNum
end getWeekdayNumOfFirstDay

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

08/22 Elgatoのハードウェアエンコーダturbo.264 HDがAppleScriptに対応

ハイビジョン対応のUSB接続ハードウェアエンコーダ「turbo.264 HD」の添付ソフトウェア(「Turbo.264 HD」)がAppleScriptに対応しているのを見つけました。

turbo264hd1.jpg
▲turbo.264 HDのPDFマニュアルより

エンコードをまとめて行わせたり、HDD内の指定フォルダ以下に存在する不必要に大きなムービーを圧縮したりするなど、AppleScriptに対応しているハードウェア/ソフトウェアならではの活用方法が山ほど思い浮かぶところですが、実物を持っていないのでちょっと、、、、

08/14 Chromiumでシークレットウィンドウを新規作成してURLをオープン

Chromiumでシークレットウィンドウを新規作成してURLをオープンするAppleScriptです。

シークレットウィンドウとは、Apple風にいえば「プライベートブラウズ」のことで、どのURLを閲覧したか履歴に残らず、閲覧中のCookieはウィンドウを閉じたあとに破棄されるとのこと。

crm002.jpg
▲シークレットウィンドウ

crm3.jpg
▲ウィンドウの右上に謎の人物が……

スクリプト名:Chromiumでシークレットウィンドウを新規作成してURLをオープン
tell application “Chromium”
  –すべてのウィンドウを閉じる
  
close every window
  
  
–新規ウィンドウを作成
  
set aWin to make new window with properties {mode:“incognito”} –シークレットウィンドウの作成
  
–ダウンロード履歴、閲覧履歴に記録されず、閲覧中のCookieもシークレットウィンドウを閉じたあとには削除される
  
  
tell aWin
    –tab 1にURLを指定
    
tell tab 1
      set URL to “http://piyocast.com/as”
      
      
–指定URLのローディングが終わるまで待つ
      
repeat
        set curStat to loading
        
if curStat = false then exit repeat
        
delay 0.1
      end repeat
      
    end tell
  end tell
end tell

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

08/14 Chromiumでウィンドウを新規作成してURLをオープン

Chromiumでウィンドウを新規作成してURLをオープンするAppleScriptです。

crm001.jpg

URLのローディングが終わるまでループで待っていますが、別にこれは必ず行わなくてはならない処理ではありません。

スクリプト名:Chromiumでウィンドウを新規作成してURLをオープン
tell application “Chromium”
  –すべてのウィンドウを閉じる
  
close every window
  
  
–新規ウィンドウを作成
  
set aWin to make new window with properties {mode:“normal”} –通常ウィンドウの作成
  
tell aWin
    –tab 1にURLを指定
    
tell tab 1
      set URL to “http://piyocast.com/as”
      
      
–指定URLのローディングが終わるまで待つ(必ず待つ必要はない。単なる趣味の問題)
      
repeat
        set curStat to loading
        
if curStat = false then exit repeat
        
delay 0.1 –CPU負荷が上がりすぎることを防ぐために処理待ちしてみた
      end repeat
      
    end tell
  end tell
end tell

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

08/12 Chromiumが最新ビルドでAppleScriptに対応

Google Chromeからグーグルの商標と自動アップデート機構を取り除いたオープンソース版「Chromium」の最新ビルドでAppleScriptに対応したというので、さっそく評価してみました。

chr1.jpg

オブジェクトは、WindowおよびWindowに内包されるTabが主なものです。新規ウィンドウのオープンも、新規タブの作成もできます。ひとつ注意が必要なのは、Windowに対して直接URLを指定することができないこと。Windowの中のtabに対してURLを設定します。

現在アクティブになっているtabの情報も取得できるし、任意のtabをアクティブにすることもできます。

オブジェクトでは、ブックマークへのアクセスが行えるのも特徴です。タイトルやURLを取得でき、ブックマークの階層構造にもアクセスできます。再帰でひととおりのブックマーク内容を取得するのも難しくないでしょう。

あとは、コピーだのペーストだの、戻るだの進むだのといった退屈なコマンドが並んでいますが、重要なのは、「copy selection」コマンドが標準装備されていること。Safariでdo java scriptコマンド経由で選択内容を取得するのに比べれば、非常に常識的です。

Tabのプロパティにローディング中か否かというものがあり、ページの表示完了をこの属性で見分けることが可能です。

ChromiumのAppleScript対応は、ひじょうに常識的なレベルで実現されている……というのが率直な感想です。

ch2.jpg

スクリプト名:Chromiumのコントロール例1
tell application “Chromium”
  properties
  
–> {name:”Chromium”, bookmarks bar:bookmark folder id 1 of application “Chromium”, frontmost:false, class:application, version:”6.0.492.0″, other bookmarks:bookmark folder id 2 of application “Chromium”}
  
  
set wCount to count every window
  
–> 1
  
  
tell window 1
    properties
    
–> {zoomed:false, miniaturized:false, name:”Google”, active tab:tab id 2 of window id 1 of application “Chromium”, mode:”normal”, miniaturizable:true, class:window, closeable:true, resizable:true, visible:true, zoomable:true, id:1, bounds:{19, 22, 928, 1156}, index:1, active tab index:1}
    
    
set tList to count every tab
    
–> 2
    
    
set aTObj to active tab
    
–> tab id 2 of window id 1 of application “Chromium”
    
    
tell active tab
      properties
      
–> {title:”Google”, URL:”http://www.google.co.jp/”, id:2, class:tab, loading:false}
    end tell
    
    
tell tab 1
      properties
      
–> {name:”Google”, URL:”http://www.google.co.jp/”, id:2, class:tab, loading:false}
    end tell
    
    
tell tab 2
      properties
      
–> {name:”Google Chrome について”, URL:”http://tools.google.com/chrome/intl/ja/welcome.html”, id:4, class:tab, loading:false}
    end tell
    
    
–Tabきりかえ
    
set active tab index to 2
    
    
set newTab to (make new tab with properties {URL:“http://piyocast.com/as/”})
    
–> tab id 10 of window id 1 of application “Chromium”
    
    
properties of newTab
    
–> {name:”", URL:”http://piyocast.com/as/”, id:12, class:tab, loading:true}
  end tell
  
end tell

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

08/12 数値リストを連続部分に分解する

数値リストを連続する部分ごとに「起点」「終点」ペアのリストに分解します。

InDesignで文字書式を変更する際に、characterを1つずつ処理していては時間の無駄なので、起点と終点を指定して書式を変更する改良を加えた際に使用しました。

本ルーチンの投入により、10倍以上の高速化に成功しました。文字数が多くなればなるほど差がつくはずです。

スクリプト名:数値リストを連続部分に分解する
set cNumList to {40, 41, 42, 43, 44, 45, 46, 47, 48, 63, 64, 85, 86, 88, 89, 90, 107, 108, 109, 110, 111, 112, 113, 114, 115}
set nList to splitNumList(cNumList) of me
–> {{40, 48}, {63, 64}, {85, 86}, {88, 90}, {107, 115}}

set cNumList to {54, 55, 56, 57, 58, 59, 60, 61, 62}
set nList to splitNumList(cNumList) of me
–> {{54, 62}}

–数値リストを、連続する値ごとにペアで「起点」「終点」リストにして返す
on splitNumList(cNumList)
  set bList to detectChangeInNumSeriesList(cNumList) of me
  
  
if length of bList is not equal to 1 then
    set prevItem to contents of first item of bList
    
    
set bList to rest of bList
    
    
set outList to {{1, prevItem}}
    
    
repeat with i in bList
      set j1 to prevItem + 1
      
set j2 to contents of i
      
set the end of outList to {j1, j2}
      
set prevItem to j2
    end repeat
    
    
set aLen to length of cNumList
    
if j2 is not equal to aLen then
      set the end of outList to {j2 + 1, aLen}
    end if
  else
    set outList to {contents of first item of bList}
  end if
  
  
set newList to {}
  
repeat with i in outList
    set j1 to item (item 1 of i) of cNumList
    
set j2 to item (item 2 of i) of cNumList
    
set the end of newList to {j1, j2}
  end repeat
  
  
return newList
  
end splitNumList

–数値リストで、値が連続しないポイントをアイテム番号のリストで返す
on detectChangeInNumSeriesList(aNumList)
  set prevNum to (first item of aNumList)
  
set bNumList to rest of aNumList
  
  
set outList to {}
  
  
set iCount to 1
  
  
repeat with i in bNumList
    set j to contents of i
    
if prevNum = (j - 1) then
      
    else
      set the end of outList to iCount
    end if
    
    
copy j to prevNum
    
set iCount to iCount + 1
  end repeat
  
  
if outList = {} then
    set outList to {{1, length of aNumList}}
  end if
  
  
return outList
  
end detectChangeInNumSeriesList

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

08/12 現在のユーザーの権限を調べる v2

現在のユーザーの権限を調べ、管理者権限があるかないかを調べて返すAppleScriptです。

Mac OS X 10.4まではnetinfo系のコマンドで調べられたのですが、10.5でnetinfoが廃止になり……代わりのコマンドで取得できるようにしてみました。10.4までと10.5および10.6以降をサポートできるようにしてみました。

現在のユーザーに管理者権限がある場合にはtrueが、ない場合にはfalseが返ってきます。

スクリプト名:現在のユーザーの権限を調べる v2
set aPriv to getCurUsersPrivileges() of me

–現在実行中のユーザーの権限を得る(管理者か、それ以外か) 10.4および10.5以降両用
–管理者だとtrueが、それ以外だとfalseが返る
on getCurUsersPrivileges()
  set aVer to system attribute "sys2" –OSメジャーバージョンを取得する(例:Mac OS X 10.6.4→6)
  
  
set current_user to (do shell script "whoami") –実行中のユーザー名を取得
  
  
if aVer > 4 then
    –Mac OS X 10.5以降の処理
    
set adR to (do shell script "/usr/bin/dsmemberutil checkmembership -U " & current_user & " -G admin users")
    
    
if adR = "user is a member of the group" then
      return true
    else
      return false
    end if
    
  else
    –Mac OS X 10.4までの処理
    
set admin_users to (do shell script "/usr/bin/niutil -readprop . /groups/admin users")
    
tell (a reference to AppleScript’s text item delimiters)
      set {old_atid, contents} to {contents, " "}
      
set {admin_users, contents} to {text items of admin_users, old_atid}
    end tell
    
    
if current_user is in admin_users then
      return true
    else
      return false
    end if
  end if
  
end getCurUsersPrivileges

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

08/10 特定月のDiary++の業務日誌の内容をExcel用Tab区切りテキストとして書き出す

特定年・月のDiary++上の業務日誌の内容をExcelに渡すためのTab区切りテキストとして書き出すAppleScriptです。

dia1.jpg

日頃から、このようにして業務内容を記録しているものですが、大人の事情で結局1か月に一度、Excelの決められたシートに転載しておく必要があります。

これをすべて手作業で行っていたのですが……どうせ自分が記録するものなので、記入のゆらぎはほとんどないはず(過信)。そこで、プログラムで処理するようにしてみました。

dia2.jpg

あんまり参考にならない内容かもしれませんが、Diary++ Xを実際に活用しているAppleScriptのサンプルとしては、(こんな風に使っているよ、という実例として)有用ではないでしょうか。

スクリプト名:特定月のDiary++の業務日誌の内容をExcel用Tab区切りテキストとして書き出す
set targYear to 2010
set targMonth to 7

set outFile to choose file name with prompt “書き出し先ファイル名を入力”

set targYearStr to (targYear as string) & “年”
set targMonthStr to targYearStr & (targMonth as string) & “月”

tell application “Diary++X”
  tell document 1
    tell category “日記”
      tell category targYearStr –2010年
        tell category targMonthStr –2010年7月
          set aList to every article
        end tell
      end tell
    end tell
  end tell
  
  
–業務日報のarticleのみ抽出(本来はタグなどで抽出すべき)
  
set bList to {}
  
repeat with i in aList
    set aCon to text contents of i
    
if aCon contains “職場関連:” then
      set the end of bList to contents of i
    end if
  end repeat
  
  
–日付とアーティクル本文のセットのリストを作成する
  
set newList to {}
  
repeat with i in bList
    set aDate to diary date of i
    
set aText to text contents of i
    
    
set the end of newList to {aDate, aText}
  end repeat
  
  
set n2List to shellSortListAscending(newList, 1) of me
  
  
n2List
end tell

–処理対象の月の日数を取得
set dLen to getMlen(targYear, targMonth) of me

–処理対象月の各日付でループし、業務日報の該当アーティクルが存在する場合には、各タスクの情報を取り出してファイル出力
–該当アーティクルが存在しない場合には日付のみのカラ行をファイル出力 するメインループ
repeat with i from 1 to dLen
  set tDate to (targYear as string) & “/” & (targMonth as string) & “/” & (i as string)
  
set aDate to date tDate
  
  
–アーティクルのデータを日付で検索
  
set findF to false
  
repeat with ii in n2List
    if item 1 of ii = aDate then
      set findF to true
      
exit repeat
    end if
  end repeat
  
  
–処理対象日のテキスト(○月○日)
  
set thisDateStr to (targMonth as string) & “月” & (i as string) & “日”
  
  
if findF = false then
    set blankDate to thisDateStr & return
    
write_to_file(blankDate, outFile, true) of me
  else
    –1日分のコンテンツ処理
    
set dList to retStartWithBlankAndEndsWithBlank(contents of item 2 of ii) of me –空白行で囲まれたブロックを日報本体としてピックアップ
    
set dList to listSweep(dList) of me –カラの行(リターンのみ)を除去
    
set firstF to true –1行目フラグ(1行目のみ日付データを出力)をtrueに
    
    
repeat with iii in dList
      set jjj to contents of iii
      
set {timeStr, targetStr, actionStr} to parseByDelim(jjj, tab) of me
      
set {sTime, eTime} to parseByDelim(timeStr, “〜”) of me
      
set diffNum to getTimeDiffNum(sTime, eTime) of me
      
      
if firstF = true then
        –1日の日付の予定の中の1行目
        
set outData to thisDateStr & tab & sTime & tab & eTime & tab & (diffNum as string) & tab & tab & tab & tab & targetStr & tab & actionStr & return
        
set firstF to false
      else
        –2行目以降
        
set outData to tab & sTime & tab & eTime & tab & (diffNum as string) & tab & tab & tab & tab & targetStr & tab & actionStr & return
      end if
      
write_to_file(outData, outFile, true) of me
    end repeat
  end if
  
  
end repeat

–リスト項目のうち、空白であった場合にはリストから除去する
on listSweep(aList)
  set bList to {}
  
set aLen to length of (item 1 of aList)
  
repeat with i in aList
    if (contents of i is not equal to “”) then
      set the end of bList to contents of i
    end if
  end repeat
  
return bList
end listSweep

–改行のみで囲まれた行をリストで取り出す
on retStartWithBlankAndEndsWithBlank(aStr)
  set mList to paragraphs of aStr
  
set wList to {}
  
  
set trimF to false
  
set tStart to 0
  
set tEnd to 0
  
set tCounter to 1
  
  
repeat with i in mList
    set j to contents of i
    
set jLen to length of j
    
if trimF = false then
      if jLen = 0 then
        set tStart to tCounter
        
set trimF to true
      end if
    else
      if jLen = 0 then
        set tEnd to tCounter
        
exit repeat
      end if
    end if
    
    
set tCounter to tCounter + 1
  end repeat
  
  
set rData to items tStart thru tEnd of mList
  
return rData
  
end retStartWithBlankAndEndsWithBlank

–指定デリミタで文字列をparseする
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

–数値にゼロパディングしたテキストを返す
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 shellSortListAscending(a, keyItem)
  set n to length of a
  
set cols to {1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1}
  
repeat with h in cols
    if (h (n - 1)) then
      repeat with i from h to (n - 1)
        set v to item (i + 1) of a
        
set j to i
        
repeat while (j h) and ((contents of item keyItem of item (j - h + 1) of a) > (item keyItem of v))
          set (item (j + 1) of a) to (item (j - h + 1) of a)
          
set j to j - h
        end repeat
        
set item (j + 1) of a to v
      end repeat
    end if
  end repeat
  
return a
end shellSortListAscending

–指定月の日数を返す
on getMlen(aYear, aMonth)
  set aYear to aYear as number
  
set aMonth to aMonth as number
  
  
set aDat to (aYear as text) & “/” & (aMonth as text) & “/1″
  
if aMonth is 12 then
    set eDat to ((aYear + 1) as text) & “/” & (1 as text) & “/1″
  else
    set eDat to ((aYear as text) & “/” & (aMonth + 1) as text) & “/1″
  end if
  
  
set eDat to date eDat
  
set eDat to eDat - 1
  
  
set mLen to day of eDat
  
return mLen
end getMlen

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

–文字列で与えられた時刻(時:分)の「差」を数値で返す(30分=0.5、15分=0.25)
on getTimeDiffNum(startTImestr, endTimeStr)
  
  
set {sH, sM} to parseByDelim(startTImestr, “:”) of me
  
set sDate to current date
  
set hours of sDate to (sH as number)
  
set minutes of sDate to (sM as number)
  
set seconds of sDate to 0
  
  
set {eH, eM} to parseByDelim(endTimeStr, “:”) of me
  
set eDate to current date
  
set hours of eDate to (eH as number)
  
set minutes of eDate to (eM as number)
  
set seconds of eDate to 0
  
  
set d to eDate - sDate
  
set h to d div 3600
  
set m to d mod 3600
  
set m2 to m / 3600
  
set m3 to h + m2
  
  
return m3
  
end getTimeDiffNum

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

08/10 改行のみで囲まれた行をリストで取り出す

箇条書きのテキストの中から、改行のみの行で始まって、改行のみに行で終わるブロックをリストで取り出すAppleScriptです。

ふだん、Diary++に記入している業務日報のデータを取り出して、別のプログラムに渡せるように加工しようとしたものです。

たいした内容のプログラムではありませんが、こうして掲載しておけば何か別の機会に再利用できるかもしれません。

スクリプト名:改行のみで囲まれた行をリストで取り出す
set aStr to "職場関連:

09:30〜12:00     ひよこ手帳  資料作成
13:00〜14:30    ひよこインク  資料作成
15:00〜17:00    世界征服計画発表会  
17:00〜18:45    ひよこインク  資料作成

"
set aRes to retStartWithBlankAndEndsWithBlank(aStr) of me
–> {"", "09:30〜12:00     ひよこ手帳  資料作成", "13:00〜14:30    ひよこインク  資料作成", "15:00〜17:00    世界征服計画発表会  ", "17:00〜18:45    ひよこインク  資料作成", ""}

–改行のみで囲まれた行をリストで取り出す
on retStartWithBlankAndEndsWithBlank(aStr)
  set mList to paragraphs of aStr
  
set wList to {}
  
  
set trimF to false
  
set tStart to 0
  
set tEnd to 0
  
set tCounter to 1
  
  
repeat with i in mList
    set j to contents of i
    
set jLen to length of j
    
if trimF = false then
      if jLen = 0 then
        set tStart to tCounter
        
set trimF to true
      end if
    else
      if jLen = 0 then
        set tEnd to tCounter
        
exit repeat
      end if
    end if
    
    
set tCounter to tCounter + 1
  end repeat
  
  
set rData to items tStart thru tEnd of mList
  
return rData
  
end retStartWithBlankAndEndsWithBlank

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

08/10 時刻文字列の差を数値で返す

時刻文字列(例:09:30)の差を数値で返すAppleScriptです。

開始時刻(例:09:30)、終了時刻(例:12:00)を渡すと、その差を数値で返します。数値で返す際には、60を1.0、30を0.5で表現して返します。2時間30分は2.5、1時間15分は1.25となります。

スクリプト名:時刻文字列の差を数値で返す
set aRes to getTimeDiffNum("09:30", "12:00") of me
–> 2.5

–文字列で与えられた時刻(時:分)の「差」を数値で返す(30分=0.5、15分=0.25)
on getTimeDiffNum(startTImestr, endTimeStr)
  
  
set {sH, sM} to parseByDelim(startTImestr, ":") of me
  
set sDate to current date
  
set hours of sDate to (sH as number)
  
set minutes of sDate to (sM as number)
  
set seconds of sDate to 0
  
  
set {eH, eM} to parseByDelim(endTimeStr, ":") of me
  
set eDate to current date
  
set hours of eDate to (eH as number)
  
set minutes of eDate to (eM as number)
  
set seconds of eDate to 0
  
  
set d to eDate - sDate
  
set h to d div 3600
  
set m to d mod 3600
  
set m2 to m / 3600
  
set m3 to h + m2
  
  
return m3
  
end getTimeDiffNum

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

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

08/10 Script Objectのparseのじっけん v4

AppleScriptのプログラムリストを解析して、Script Objectのリストアップを行うAppleScriptです。

AppleScriptのプログラムを解析する、自己解析系のAppleScriptを作りかけて……複数のAppleScript書類に存在しているハンドラ同士のdiffを取ろうとしていました。同じ名称のハンドラで、処理内容が異なる場合には困るので、比較しようと考えたわけです。

ハンドラ同士のDiffを取ろうと考えたときに、普通のハンドラなら簡単でよいのですが……

スクリプト名:script1
on test()
  display dialog “Test”
end test

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

ここに、Script Objectの宣言が加わってくると面倒です。さまざまな大きめのプログラムをつなげるときに、お互いにハンドラの重複があったりして統合が面倒という時があって、そういう時にはscript objectで論理分割して、同じハンドラ名称があっても別物扱いするようなことが……よくあります。

スクリプト名:script2
script scriptObj1
  on test()
    display dialog “Test”
  end test
end script

script scriptObj2
  on test()
    display dialog “Test”
  end test
end script

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

このぐらいならまだかわいげがあるのですが、Script文は入れ子にできるので、これに対処する必要があります。

スクリプト名:script21
script scriptObj1
  on test()
    display dialog "Test@scriptObj1"
  end test
end script

script scriptObj2
  on test()
    display dialog "Test@scriptObj2"
    
    
script scriptObj21
      on test()
        display dialog "Test@scriptObj21"
      end test
      
script scriptObj211
        on testr()
          display dialog "Test@ scriptObj211"
        end testr
      end script
    end script
    
  end test
end script

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

下のScriptをscript 21に対して実行すると、

–> {{”scriptObj1″, 1, 5}, {”scriptObj2″, 7, 23}, {”scriptObj21″, 11, 20}, {”scriptObj211″, 15, 19}}

といった結果が返ってきます。

このScript文を考慮してハンドラのリストアップを行うようにするといい感じでしょうか。

スクリプト名:Script Objectのparseのじっけん v4
–v4では、ネスティングしたScript文から再帰でオブジェクト名をピックアップ。一応の完全体
–v3では、Script文のネスティングに対応(ピックアップまで対応)
–v2では、Script文のネスティングに対応(対応しただけ)

global scObjList –このへん、必須(結果を値渡しではなく、グローバル変数へアクセスで行うため)
global aScriptList, a_r –ここも必須(再帰時にアクセスするのと、高速化のため)

–サンプルのAppleScript書類の内容をピックアップ
tell application “AppleScript Editor”
  set nameList to name of every document
  
set selDoc to choose from list nameList
  
tell document (contents of first item of selDoc)
    set a to contents
  end tell
end tell

set aScriptList to paragraphs of a
set a_r to a reference to aScriptList –間接アクセスで処理の高速化を行う

set allLen to length of a_r
set scObjList to {}

pickUpScriptObjectFromList(1, allLen) of me
set objList to shellSortListAscending(scObjList, 2) of me

objList

on pickUpScriptObjectFromList(startLineNum, endLineNum)
  
  
set curScriptObj to {} –name, startLine, endLiine
  
set nestingCounter to 0
  
set nestedF to false
  
set lineCounter to startLineNum
  
set findF to false –script object末尾検索フラグ。trueで末尾の”end script”の検索中
  
  
repeat with i from startLineNum to endLineNum
    
    
set ii to contents of (item i of a_r)
    
    
ignoring hyphens, punctuation and white space
      –Script文の開始位置を走査中
      
if findF = false then
        –Script文をみつけた場合
        
if ii begins with “script” then
          set aRes to parseScriptObjectName(ii) of me
          
set curScriptObj to {aRes, lineCounter, 0}
          
set findF to true
        end if
        
        
      else if findF = true then
        –Script文の末尾を走査中モード
        
if ii begins with “script” then
          set nestingCounter to nestingCounter + 1
          
set nestedF to true
          
        else if ii begins with “end script” then
          if nestingCounter = 0 then
            set item 3 of curScriptObj to lineCounter
            
set the end of scObjList to curScriptObj
            
            
if nestedF = true then
              –このへんで、Script Objectのネスティングに対して再帰でアプローチする
              
pickUpScriptObjectFromList((item 2 of curScriptObj) + 1, (item 3 of curScriptObj) - 1) of me –再帰時に捜索範囲を前後ともに1行ずつ狭める
            end if
            
            
set findF to false
            
set nestingCounter to 0
            
set nestedF to false
            
          else
            set nestingCounter to nestingCounter - 1
            
          end if
        end if
      end if
    end ignoring
    
    
set lineCounter to lineCounter + 1
    
  end repeat
end pickUpScriptObjectFromList

–与えられた1行分のテキスト(おそらく、script文の宣言部分)から、Script Object名称をparseする
on parseScriptObjectName(aText)
  set aOffst to offset of “script “ in aText
  
if aOffst = 0 then return “”
  
set aRes to text (aOffst + (length of “script “)) thru -1 of aText
  
return aRes
end parseScriptObjectName

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

–シェルソートで入れ子のリストを昇順ソート
on shellSortListAscending(a, keyItem)
  set n to length of a
  
set cols to {1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1}
  
repeat with h in cols
    if (h (n - 1)) then
      repeat with i from h to (n - 1)
        set v to item (i + 1) of a
        
set j to i
        
repeat while (j h) and ((contents of item keyItem of item (j - h + 1) of a) > (item keyItem of v))
          set (item (j + 1) of a) to (item (j - h + 1) of a)
          
set j to j - h
        end repeat
        
set item (j + 1) of a to v
      end repeat
    end if
  end repeat
  
return a
end shellSortListAscending

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

08/07 エイリアス書類からオリジナルファイルの情報を取得する

エイリアス書類からオリジナルファイルの情報を取得するAppleScriptです。

スクリプト名:エイリアス書類からオリジナルファイルの情報を取得する
set aFile to choose file

tell application "Finder"
  try
    set origI to (original item of aFile) as alias
  on error
    –エイリアス書類のオリジナルファイルが削除されてた場合にはエラーになる
    
set origI to false
  end try
end tell

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

08/06 指定フォルダ内にエイリアス書類を作成

指定フォルダ以下にあるXcodeプロジェクト書類をspotlightでリストアップし、別途指定した出力先フォルダにエイリアス書類を出力するAppleScriptです。

「com.apple.xcode.project」の部分を書き換えれば、お好きな書類をリストアップ可能です。

alias1.jpg

エイリアス書類をAppleScriptから作成することは、(自分は)めったにないのですが……たとえば、何らかの条件に合致するファイル群(あるいは多量のパス情報)を抽出したとして、そのエイリアス書類を特定のフォルダに作成して入れておくと、いちいちテキストを見ながらFinder上で指定フォルダに移動してファイルをオープン……といった煩雑な処理をしなくて済みます。

ただ、実戦でもそこまでやることは少ないので、エイリアス書類をAppleScriptから作成するケースは非常に少ないのですが、やり方を再確認しておこうと考え、ちょっと書いてみました。

エイリアス書類を作成するのに、「make new alias file」で作成しています。以前(Classic Mac OS時代)には異なる記法だったような気がするのですが、もはやClassic Mac OSを使うこともないので気にしないことに。

エイリアス書類の作成コマンドと、エイリアス書類のファイル名が重複してしまった場合の回避処理がみどころです。ただし、ファイル名重複時の回避処理はそれほど気合いが入っておらず、同名のファイルが256個以上出力される場合にはエラーになります。そこは、256以上の数値に変えてみるか……抜本的に重複回避処理を見直してみたほうがよいでしょう。

スクリプト名:指定フォルダ内にエイリアス書類を作成
set aFol to choose folder with prompt "エイリアスファイルを出力するフォルダを選択"
set bFol to choose folder with prompt "Xcode書類を探すフォルダを選択"

set bList to getFileListWithSpotLight("kMDItemContentType", "com.apple.xcode.project", bFol) of me –Xcodeのファイルをクリエータコードで検索
repeat with i in bList
  set aFolStr to aFol as string
  
makeAliasFileInAFolder(i, aFolStr) of me
end repeat

–指定フォルダ内に指定名称のエイリアス書類を作成する
on makeAliasFileInAFolder(anAlias, outFol)
  tell application "Finder"
    set aName to name of anAlias
    
tell folder outFol
      if file aName exists then
        set c to makeUniqueName(outFol, aName) of me
      end if
    end tell
    
    
set newalias to make new alias at folder outFol to anAlias
    
set name of newalias to aName
  end tell
end makeAliasFileInAFolder

–指定フォルダ下に存在できる、指定名称+数字の名前の(かぶらない)ファイル名を作成
on makeUniqueName(aFolder, origName)
  set pureFileName to retFileNameWithoutExt(origName) of me
  
set anExt to retExtNameFromFilenameStr(origName) of me
  
  
tell application "Finder"
    tell folder aFolder
      repeat with i from 1 to 256
        if not (exists of file (pureFileName & " " & (i as string) & "." & anExt)) then
          return (pureFileName & " " & (i as string) & "." & anExt)
        end if
      end repeat
    end tell
  end tell
end makeUniqueName

–ファイル名から拡張子を外す
on retFileNameWithoutExt(fileNameStr)
  set fLen to length of fileNameStr
  
set revText to (reverse of (characters of fileNameStr)) as string –逆順テキストを作成
  
set anOffset to offset of "." in revText
  
set fRes to text 1 thru (fLen - anOffset) of fileNameStr
  
return fRes
end retFileNameWithoutExt

–ファイル名文字列から拡張子のみ取得する
on retExtNameFromFilenameStr(fileNameStr)
  set fLen to length of fileNameStr
  
set revText to (reverse of (characters of fileNameStr)) as string –逆順テキストを作成
  
set anOffset to offset of "." in revText
  
set fRes to text (fLen - anOffset + 1) thru -1 of fileNameStr
  
return fRes
end retExtNameFromFilenameStr

–指定階層下で、指定クリエータコードのファイルを取得(Mac OS X 10.4.x以上で動作)
on getFileListWithSpotLight(aMetaDataItem, aParam, startDir)
  set sDirText to quoted form of POSIX path of startDir
  
set shellText to "mdfind ‘" & aMetaDataItem & " == \"" & aParam & "\"’ -onlyin " & sDirText
  
try
    set aRes to do shell script shellText
  on error
    return {}
  end try
  
set pList to paragraphs of aRes
  
set aList to {}
  
repeat with i in pList
    set aPath to POSIX file i
    
set aPath to aPath as alias
    
set the end of aList to aPath
  end repeat
  
return aList
end getFileListWithSpotLight

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

08/06 近似色を求めるサンプル

RGBベースで近似色を求めるAppleScriptのサンプルです。

choose colorでカラーピッカーダイアログから指定した任意の色と、あらかじめ用意しておいた色データリストとの間でRGBそれぞれのチャンネルの数値の差の絶対値を求めてポイント化し(ただ、R-difference, G-difference, B-differenceを足しているだけ)、ポイントが少ない(=距離が近い=近似色)順にソートして求めています。

choose colorコマンドに関しては……RGBの値を求めるときにでも、0〜255ではなく0〜65535までの値が返ってくるので、256で割って使っています。

本サンプルでは数個の色リストしか記述していませんが、実戦では数百とか1,000以上のカラーリストから近似色を求めることになります。そのために、近似色の計算ループをa reference toによる間接アクセスで高速化してあります。

「近似色を求める」……と、漠然と考えると一体何のことやら分かりませんが、3つの数値がペアになったデータの最も近い値、と考えると簡単です。

スクリプト名:近似色を求めるサンプル
–近似色検索(RGBベース)
set colList to {{“トウガラシ”, “COL-1″, 128.5, 0.0, 0.0}, {“アスパラガス”, “COL-2″, 128.5, 128.5, 0.0}, {“クローバー”, “COL-3″, 0.0, 128.5, 0.0}, {“ティール”, “COL-4″, 0.0, 128.5, 128.5}, {“ミッドナイト”, “COL-5″, 0.0, 0.0, 128.5}}

set colL_r to a reference to colList

set {aR, aG, aB} to choose color
set aaR to aR div 256
set aaG to aG div 256
set aaB to aB div 256

set aRes to {}
repeat with i in colL_r
  set {iColName, iColCode, iR, iG, iB} to i
  
  
set calR to absNum(aaR - iR)
  
set calG to absNum(aaG - iG)
  
set calB to absNum(aaB - iB)
  
  
set the end of aRes to {contents of i, calR + calG + calB}
end repeat

set bRes to shellSortListAscending(aRes, 2) of me

set cRes to items 1 thru 3 of bRes

–ここから結果表示のための処理
set tList to {}
set the end of tList to listToText({aaR, aaG, aaB}) of me

repeat with i in cRes
  set the end of tList to listToText(contents of i) of me
end repeat

–結果表示。何かを選択することを目的としているわけではない
choose from list tList with prompt “選択した色の近似色です”

–絶対値を求める
on absNum(q)
  if q is less than 0 then set q to -q
  
return q
end absNum

–シェルソートで入れ子のリストを昇順ソート
on shellSortListAscending(a, keyItem)
  set n to length of a
  
set cols to {1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1}
  
repeat with h in cols
    if (h (n - 1)) then
      repeat with i from h to (n - 1)
        set v to item (i + 1) of a
        
set j to i
        
repeat while (j h) and ((contents of item keyItem of item (j - h + 1) of a) > (item keyItem of v))
          set (item (j + 1) of a) to (item (j - h + 1) of a)
          
set j to j - h
        end repeat
        
set item (j + 1) of a to v
      end repeat
    end if
  end repeat
  
return a
end shellSortListAscending

–choose from listで表示するためのサブルーチン

–リストをテキストに
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)) –ちょっと再帰処理
      
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

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

08/06 choose folderなどの動作を変更する

choose folder/choose fileなどの動作を変更するAppleScriptです。

AppleScriptでプログラムを組んでいて、試作版のときには処理対象を指定するのにchoose folderコマンドでダイアログを表示してフォルダを選択したりしていますが…………

プログラムも完成して、動作内容も決まって運用段階になってくると、いちいちフォルダを選択するのが面倒に。

プロパティで値を持たせるのが普通のやりかたですが、プログラムが巨大になってくると、いちいちすべて書き換えるのが面倒になって……くるかもしれません(さすがに3,000行を超えてくると、ちょっとプログラムの中身をすべて書き換えるのが面倒に、、)。

そこで、「こんな風に書き換えてもいいよね」というサンプルを作ってみました。「choose folder」コマンドに対して、「on choose folder」というハンドラを定義すれば、コマンドの動作を乗っ取ることができます。choose folderコマンドのパラメータからどの場所で使用されたchoose folderコマンドかを判別しています。

logコマンドを乗っ取って別の動作を行わせるサンプルは以前紹介していましたが、logやchoose folderやchoose fileのほかに、

say、display dialog、log、do shell script、activate、launch、round、beep、set volume、set the clipboard to、ASCII number、ASCII character、read、write、open for access、close access、choose application、get eof、set eof、random number、path to、quit、print

といった標準搭載のAppleScript命令の多くが、同様の記述によってコマンド内容を乗っ取って別の動作を行えることを確認しました。

スクリプト名:choose folderの動作を変更する
set a to choose folder "処理データが入っているフォルダ"
set b to choose folder "処理結果を出力するフォルダ"
set c to choose file "テンプレートファイルを選択"

on choose folder aMes
  if aMes = "処理データが入っているフォルダ" then
    set aPath to (((path to desktop from user domain) as string) & "procFol:")
  else if aMes = "処理結果を出力するフォルダ" then
    set aPath to (((path to desktop from user domain) as string) & "outFol:")
  end if
  
return aPath as alias
end choose folder

on choose file aMes
  if aMes = "テンプレートファイルを選択" then
    set aFile to (((path to desktop from user domain) as string) & "ui1.jpg")
  end if
  
return aFile as alias
end choose file

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

08/03 GUI部品にAppleScriptを貼り付ける「UI Actions 2.0.0」

PFiddlesoft(旧称PreFab Software。現在の名称は読み方が分からない)のBill Cheesemanといえば、AppleScript界ではビッグネームなわけで、彼が作る(AppleScript系の)ソフトウェアの数々は、一癖も二癖もあってなかなか日本の一般的なMacユーザーには理解しがたい種類のものですが、この「UI Actions」はその最たるものでしょう。

ちょうどこの2010年7月末に新バージョン2.0.0がリリースになったのでご紹介します。

AppleScriptを水や空気のごとく読み書きする人間にしか、その価値や機能が理解できないに違いありません(間違っても、日本のMac系雑誌で紹介されたりしないレベル)。

ui1.jpg

かなり乱暴な言い方をすれば、このUI Actionsは……Folder Actionsのように、指定したアプリケーションのGUI部品にAppleScriptをattach(いい日本語訳が思いつきません。貼り付ける、という感じ?)するものです。指定アプリケーション上の指定のGUI部品の状態が変わったりした時に(notifyが発生した時に)、指定のScriptが実行されます。

ui2.jpg

たとえば、Safariの「Safariについて」というメニュー項目に指定のAppleScriptをattachしておくと、「Safariについて」のメニュー項目が実行されるたびに、指定のAppleScriptが実行されます。

ui3.jpg

uiaction1.png

ui9.jpg

ウィンドウが新規作成されたり、ウィンドウのリサイズが行われたり、特定のボタンが押されたりしたら、指定のAppleScriptが実行されるようになります。

すぐには思いつきませんが、本ソフトウェアを使ってアプリケーションの機能を拡張していくと、すさまじく便利な環境が作れそうです。たとえば、

・Safariで指定のサイトをオープンした時にだけ、そのサイトのダウンロードリンクを収集して、そのサイトに掲載されているダウンロードアイテムをすべてバックグラウンドでダウンロード
・Finderで新規ウィンドウをオープンしたら、ウィンドウのサイズをきっちりそろえる
・Excelで新規ファイルを保存するたびに、書類情報をテキストに保存しておく
・AppleScript Editorで新規ドキュメントを保存するたびに、Omni OutlinerとかFileMaker Proにソースコードの情報を登録しておく

などの使い方が考えられます(あっ、最後の例はなにげに便利)。

ちなみに、UI Actionsをインストールしたマシン上でしかattachは有効になりません。対象アプリケーションのバージョンアップ(アップデート)を行った場合でも、インストール先のパスが変わらなければ、attachは維持されるのではないでしょうか。

「UI Actions」は、UI Actionの監視を行うバックグラウンドアプリケーション「UI Actions」(/Library/Scripting Additions/UI Actions.app)と、その設定用GUIアプリ「UI Actions Setup」から構成されます。

ui5.jpg

インストールするとOSの動作が重くなるのでは? と、心配していたのですが……notificationの仕組みを利用しているためか、ほとんど気にならないレベルです。アクティビティモニタでプロセスのCPU占有率を調べてみても、ご覧のとおりです(Core i7 2.66GHzのMacBook Pro上での実行結果)。

「UI Actions Setup」を使わなくても、「UI Actions」にAppleScriptから命令を投げることで、Scriptのattach/detachを行うことが可能になっています(UI Actions自体がAppleScriptから制御可能な、「Scriptableな」アプリケーション)。

ui6.jpg

また、UI Actionsから呼び出されたAppleScript内で、attachされたGUI部品の情報がpropertiesで取得できたりはしませんが(そこまでできるとよかったのに)、GUI Scripting経由でそうした情報を取得して条件判断してScriptを実行したり、Script実行が完了するまで指定Scriptの実行を禁止するといった制御が可能です(同じメニューを何回も実行されたような場合への対策)。

もう少しユーザーの間口を広げる(=利用者層を広げる)ためには、AutomatorのActionをattachできるようにするとか、attachの仕方や条件を「menu_item_selected」といった生のnotificationのイベント名ではなく「メニューが選択されたら」といった普通の文章にしてみるとか(逆に分かりづらいという説も)、attachの仕方をもっとグラフィカルにして、attachされたメニューなどにスクリプトアイコンが追加される……とかいった改良が必要になることでしょう(それは大変すぎる……)。

Macの使い方におっそろしく便利な機能を提供する(かもしれない)本ソフト、お値段たったの35ドル。けっこう高齢なBill Cheesemanが健在なうちにぜひご購入を。

07/31 InDesign CS3で、指定ScriptLabelの中のテキストの、指定行の指定文字以降を行末まで取得

InDesign CS3で、現在オープン中の最前面のドキュメント上の指定のScript Labelがついているtext frame中のテキストで、指定行目の指定文字以降の文字を行末まで取得するAppleScriptです。

「指定文字」が存在しなかった場合には行ごと返します。

id1.jpg

スクリプト名:InDesign CS3で、指定ScriptLabelの中のテキストの、指定行の指定文字以降を行末まで取得
set aRes to getLabeledDataAndPickUp("hiyokoMaker", 2, "/") of me
–> "ボボ・ブラジル"

set aRes to getLabeledDataAndPickUp("hiyokoMaker", 2, "ぴよぴよ") of me
–> "■ひよこの原産地/ボボ・ブラジル"

–指定ScriptLabelの中のテキストの、指定行の指定文字以降を行末まで取得
on getLabeledDataAndPickUp(aScriptLabel, aLineNum, aMarker)
  set aRes to retLabeledData(aScriptLabel) of me
  
set aList to paragraphs of aRes
  
set aLine to contents of item aLineNum of aList
  
set aPos to offset of aMarker in aLine
  
set aText to text (aPos + 1) thru -1 of aLine
  
return aText
end getLabeledDataAndPickUp

–指定のScript Labelのデータ(Contents)を取り出す
on retLabeledData(aScriptLabel)
  tell application "Adobe InDesign CS3"
    tell document 1
      try
        set aObj to every page item whose label is aScriptLabel
        
        
set targObj to first item of aObj
        
set aRes to contents of text 1 of targObj
      on error
        return "<error>"
      end try
    end tell
  end tell
end retLabeledData

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

07/31 現在のドキュメント上のアイテムをすべてグループ解除

InDesign CS3で、現在オープン中の最前面のドキュメント上のオブジェクトをすべてグループ解除するAppleScriptです。

スクリプト名:現在のドキュメント上のアイテムをすべてグループ解除
ungroupAll() of me

on ungroupAll()
  tell application "Adobe InDesign CS3"
    tell document 1
      tell every page
        repeat
          set gList to every group
          
if gList = {} then exit repeat
          
tell every group to ungroup
        end repeat
      end tell
    end tell
  end tell
end ungroupAll

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

07/31 リーダー罫文字のあとの文字を取得する

与えられた文字列のうち、リーダー罫として指定した文字(連続する「…」文字など)のあとに出現する文字を返すAppleScriptです。リーダー罫が文字列中に存在しなかった場合にはそのまま返します。

InDesignの書類からデータを取り出す際に使用したものです。

スクリプト名:リーダー罫文字のあとの文字を取得する
set a to "ひよこさん募集………………………………………………160名"
set b to retAfterRuledLineCharacter(a, "…") of me
b
–> "160名"

set a to "ひよこさんの募集は終了しました"
set b to retAfterRuledLineCharacter(a, "…") of me
b
–> "ひよこさんの募集は終了しました"

–リーダー罫文字のあとの文字を取得する
on retAfterRuledLineCharacter(a, aRulerLineChar)
  if a does not contain aRulerLineChar then
    return a
  end if
  
  
set cList to characters of a
  
set aLen to length of a
  
  
set rList to reverse of cList
  
set rText to rList as string
  
set rOffset to offset of aRulerLineChar in rText
  
  
set resText to text (aLen - rOffset + 2) thru -1 of a
  
  
return resText
end retAfterRuledLineCharacter

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

07/25 指定フォルダ以下のファイル作成日付をファイル修正日にコピーする

指定フォルダ以下のすべてのファイルの作成日付の情報を取得し、最終修正日の情報にコピーするAppleScriptです。

10数年前に撮影したQuickTake 100のデジタル写真データをPICTからJPEGにコンバートしたのはよかったのですが、iPhotoにインポートしたときに作成日が反映されなかったので、ためしに作ってみました。

なお、実行のためには、Developper Toolsがインストールしてある必要があります。

これでダメなら、本当にEXIFのデータをいじくる必要が(汗)

スクリプト名:指定フォルダ以下のファイル作成日付をファイル修正日にコピーする
set aFol to choose folder
set fList to {}
set f_r to a reference to fList

–外付けHDDをSpotlightの処理対象外に指定していたので、mdfindコマンドを使わなかった
tell application “Finder”
  set fList to entire contents of aFol as alias list
end tell

–メインループ
repeat with i in f_r
  set j to contents of i
  
try
    set aInfo to info for j
    
set aF to folder of aInfo
  on error
    set aF to false
  end try
  
  
if aF = false then
    changeModDateToCreationDate(j) of me
  end if
end repeat

–作成日付の情報を修正情報にコピーする
on changeModDateToCreationDate(aFile)
  tell application “Finder”
    set aInfo to info for aFile
    
set creDate to creation date of aInfo
  end tell
  
set aDateStr to makeMMDDYYYYhhmmssStr(creDate) of me
  
  
set aP to POSIX path of aFile
  
  
try
    do shell script “/usr/bin/SetFile -m “ & quoted form of aDateStr & ” “ & quoted form of aP –修正日
  end try
end changeModDateToCreationDate

–DateオブジェクトからMM/DD/YYYY hh:mm:ssの形式の文字列を返す
on makeMMDDYYYYhhmmssStr(aDate)
  –Dateオブジェクトから各要素を取り出す
  
set yStr to (year of aDate) as string
  
set mStr to (month of aDate as number) as string
  
set dStr to (day of aDate) as string
  
set hhStr to (hours of aDate) as string
  
set mmStr to (minutes of aDate) as string
  
set ssStr to (seconds of aDate) as string
  
  
–桁数を合わせる
  
set y2Str to retZeroPaddingText(yStr, 4) of me
  
set m2Str to retZeroPaddingText(mStr, 2) of me
  
set d2Str to retZeroPaddingText(dStr, 2) of me
  
set hh2Str to retZeroPaddingText(hhStr, 2) of me
  
set mm2Str to retZeroPaddingText(mmStr, 2) of me
  
set ss2Str to retZeroPaddingText(ssStr, 2) of me
  
  
return (m2Str & “/” & d2Str & “/” & y2Str & ” “ & hh2Str & “:” & mm2Str & “:” & ss2Str)
end makeMMDDYYYYhhmmssStr

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

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

07/25 指定フォルダ以下のPICTファイルをGraphicConverterでJPEGに変換

指定フォルダ以下のすべてのPICTファイルをGraphicConverter(v6.7.3)でJPEGファイルに変換するAppleScriptです。

今をさかのぼること10数年前に発売された、初の商用デジタルカメラ「QuickTake 100」で撮りためた写真を128MBのMO十数枚に保存してありました。

desk.jpg
▲1995/2/7の写真。SPARCstation IPXが写っている

iicx.jpg
▲Macintosh IIcxが写っている。古い……

Preview.appの64ビット起動モード([6][4]を押しながら起動……ではなく、Mac OS X 10.6の32ビット起動&通常アプリ起動)ではQuickTakeのネイティブ記録フォーマットであるPICT(QuickTake PICT)をオープンできなくなっていました。

pr1.jpg

そこで、MOドライブが動くうちに読み取って、JPEGにフォーマット変換しておこうと思い立ち、Graphic ConverterでPICTをオープンできることを確認しました。

gc1.jpg

Graphic Converterは昔からAppleScriptに対応していたのですが、あまりASからコントロールする機会はありませんでした。ひととおり最低限の処理を記述して、1フォルダ分のデータで処理テストを実施。Classic Mac OSのデータをMac OS Xにコピーしてそのまま処理したので、一部で不可解な挙動(file typeを取得する際にエラーが出るとか)がありましたが、とくに問題なくデータ変換できました。

ファイルの作成日が引き継がれないのでは? と心配していましたが、修正日が変更されるだけでとくに変わることはなかったので、あらかじめ作っておいた作成日変更のScriptは使わずじまいに。

Graphic ConverterのScriptingを行って不思議だったのは、画像に対してDocumentオブジェクトでアクセスできずに、Windowオブジェクトを通じてアクセスする必要があること。複数枚の画像をオープンして相互にデータのやりとりを行うような場合には、ちょっと辛い(どうやってWindow同士の区別を行うのか???)感じがします。

あとは、Grachic Converterのプロセスを非表示にして処理を行ったものの、ファイルオープンのアニメーションだけは表示されてしまい、そのあたりは少々不満を覚えたぐらいでしょうか。

スクリプト名:指定フォルダ以下のPICTファイルをJPEGに変換
set aFol to choose folder
set fList to {}
set f_r to a reference to fList

–外付けHDDをSpotlightの処理対象外に指定していたので、mdfindコマンドを使わなかった
tell application “Finder”
  set fList to entire contents of aFol as alias list
end tell

–Graphic Converterを非表示状態に
tell application “System Events”
  tell process “GraphicConverter”
    set visible to false
  end tell
end tell

–メインループ
repeat with i in f_r
  set j to contents of i
  
try
    set aInfo to info for j
    
set aF to folder of aInfo
    
  on error
    set aF to false
  end try
  
  
if aF = false then
    try
      –古いMac OSのファイルにアクセスしたときに、問題が発生することがあったために対処した
      
with timeout of 10 seconds
        tell application “Finder”
          set aType to file type of j
        end tell
      end timeout
      
      
if aType = “PICT” then
        convToJPEG(j) of me
      end if
    end try
    
  end if
end repeat

–指定のファイルをGCでオープンしてJPEGでSave
on convToJPEG(aFile)
  tell application “GraphicConverter”
    –オープン中のドキュメントをすべてクローズ
    
try
      close every window saving no –GCにはDocumentというオブジェクトは定義されていない。すべてwindowで操作
    end try
    
    
silentopen aFile –openコマンドだとエラーやダイアログ表示を行うが、silentopenコマンドだとメッセージが出ないらしい。ただし、非表示オープンモードではない
    
    
set saveRes to (save window 1 in aFile as JPEG without makeCopy and wwwready)
    
–> 保存成功の場合には0が返る
    
    
–オープン中のドキュメントをすべてクローズ
    
try
      close every window saving no
    end try
  end tell
  
  
–保存したファイルにJPEGの拡張子を付ける
  
if saveRes = 0 then
    tell application “Finder”
      set aName to name of aFile
      
set aName to aName & “.jpg”
      
set name of aFile to aName
    end tell
  end if
  
end convToJPEG

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