Archive for 8月, 2010

2010/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

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

2010/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

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

2010/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

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

2010/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

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

2010/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

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

2010/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

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

2010/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

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

2010/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

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

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

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

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

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

2010/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

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

2010/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

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

2010/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

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

2010/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

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

2010/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

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

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

2010/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

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

2010/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

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

2010/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

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

2010/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

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

2010/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

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

2010/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

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

2010/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

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

2010/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が健在なうちにぜひご購入を。