Archive for 6月, 2013

2013/06/29 OSX 10.7, 10.8のAppleScriptリリースノートがやっと掲載される(2/2)

OS X 10.9のリリースを秋に控えたこの時期になって、ノコノコ掲載されたAppleScriptリリースノートの続編(10.8)です。機能追加やバグ修正などは、リリースノートを読まないと分らないので、OSのリリースと同時に掲載されることが望ましいのですが……Cupertinoには世間とは別の謎の常識が存在しているようです。

自分はOS X 10.7を海外のScripterの評判から「パスすべきOS」と判断し、メイン環境としてはほとんど使いませんでした。そのため、10.6からいきなり10.8に移行して、環境の激変に目を回していましたが……それ以前のバージョンのOSをいまだに使っている人は、次の10.9に乗り損なうと、取り返しのつかない状態になると思います。

OS X 10.8機能追加

・AppleScript Editor
10.8のAppleScriptエディタは書類の取扱いに関して数多くの機能追加が行われている。
「Auto Save」書類への変更は自動的に保存される。コンパイル(構文確認)前の入力であっても同様。
「Versions」書類の以前のバージョンに簡単に戻れる。書類のタイトルバーから前バージョンへの移行が行える。
「Exporting」ファイルメニューから選択可能な「書き出す…」コマンドによって、現在編集中のScript/Scriptアプリケーション(アプレット)の配布用コピーを保存可能になった。「書き出す…」コマンドは実行専用のScriptを書き出す唯一の方法になった。編集可能なオリジナルScriptを上書き保存してしまう危険を減らすことになる。
「Bundle Identifier」アプレット(Script Application)時のバンドルの内容設定用ドロワーにて、あらたにバンドルID設定用フィールドを設けた。AppleScriptエディタはデフォルト値をフィールドに用意しておくが、ユーザーには配布用Scriptアプリケーションの識別用にカスタマイズすることが推奨される。バンドルIDを設定することが推奨されるものの、以前のバージョンまではInfo.plistを直接書き換える作業が必要とされてきた。

OS X 10.8 バグ修正

・AppleScript

リスト型変数(list)を文字(string)に変換する際、リスト中のアイテムが1つでも変換不可であればエラーにするようにした。従来はこれが、一部分のみ変換成功すると主張していた

「div」および「mod」演算子でIEEE754の演算ルーチンを使って結果を出すようにした。これは、OS X上の他のプログラミング言語との整合性のための修正。従来バージョンでは、正しくない答を正しくするように演算結果をごまかしていた。

・Standard Additions
「time to GMT」(グリニッジ標準時との時差)コマンドはタイムゾーンの変化とサマータイム時間の施行を考慮するようになった

「open for access」コマンドはスラッシュ(/)入りのファイル名のファイルを作成可能になった

互換性に関する注意点

Sandbox化したアプリケーション(Mountain Lion上のテキストエディットなど)にコマンドを送信する場合、パラメーターでファイル参照を行っているものは、ファイル記述、POSIXパス記述である必要がある。単なるパスの文字列で指定すると、ファイルにアクセスできない。たとえば、file “Macintosh HD:Users:me:sample.txt”やchoose fileの実行結果(aliasが得られる)は受け付けられるが、”/Users/me/sample.txt”では受け付けられない。

(以下、Developper NoteとしてAppleScript AppletへのCode Siginingの仕方と、AppleScript対応のアプリケーションのSandbox化の留意点、AppleScript対応機能がSandbox化により影響を受けないことが記載されている)

2013/06/29 OSX 10.7, 10.8のAppleScriptリリースノートがやっと掲載される(1/2)

10.6以降、AppleScriptのリリースノートが掲載されておらず、ひじょーに「何やってんの???」な状態でした。まるで、90年代の「ダメだった頃のApple」が帰ってきたような不始末です。

本日Twitter経由で情報がまわってきてリリースノートが掲載されたことを確認しました。

■OS X 10.7機能追加
・Script Templates
AppleScriptエディタ上で、テンプレートからの新規Script作成をサポートするようになった。各テンプレート(スケルトン)の書類は、「テンプレートから新規作成」コマンドによってそれぞれの目的に応じてデザインされたものを選択可能。ユーザー自身が作成したAppleScriptをテンプレートとして使用する場合には、/Library/Application Support/Script Editor/Templatesフォルダに入れること。コマンドキーを押しながらテンプレートの選択を行うと、選択したテンプレートをFinder上に表示する。

・AppleScriptObjC in AppleScript Editor
AppleScriptエディタが「Cocoa-AppleScript Applet」テンプレートを提供。これは、AppleScriptObjCベースのアプリケーションであり、テンプレート内には従来からのなじみのある「runハンドラ」「openハンドラ」(ともにアプレット保存時にのみ有効)と同等の働きをする記述が盛り込まれている。結果として、ユーザーはCocoa APIを呼び出す機能が追加されたアプレットをAppleScriptエディタ上で記述することが可能になった。

・Other Enhancement
System EventsのProcess Suite(GUI Scripting用語Suite)に新たに「pop over」のクラスが追加された。これは、OS X Lionでサポートされた新たなpop overウィンドウに対応するためのもの。

path to に「services folder」が追加された。ユーザーのServicesフォルダを返す。

■OS X 10.7バグ修正

・AppleScript
AppleScriptのパフォーマンスが向上した
AppleScriptは「text item delimiters」を参照する際にignoring節が指定されている場合にでもハングアップしないようになった
AppleScriptはSystem Eventsのパス情報をログイン直後に識別するようになり、ログイン直後に実行した際にSystem Eventsのパスをユーザーに尋ねることがなくなった

・Standard Additions
「mount volume」コマンドはパスワードが31文字以上の場合でも正しく動作するようになった
「beep」コマンドはScriptの実行末尾で実行された場合でもビープ音(警告音)を鳴らすようになった

・AppleScript Editor
巨大な埋め込みリソースを含むScriptバンドルやアプレットバンドルを保存する場合のファイル保存パフォーマンスが向上
「Save As…」(別名で保存…)コマンドで埋め込みリソースを正しくコピーするようになった

■互換性に関する注意点

セキュリティ上の理由により、AppleScriptはinitializer-basedなScripting Additionsをサポートしなくなった。Scripting AdditionsはMac OS X 10.5, Leoardで紹介されたInfo.plistスキームを利用する必要がある。
もし、ユーザーが現在ではサポートされないinitializer basedなScripting Additionを入れている場合、コンソール.appにて以下のようなメッセージが表示される。
OpenScripting.framework - scripting addition “/Library/ScriptingAdditions/Potrzebie.osax” declares no loadable handlers.

2013/06/26 ISO形式日付→dateオブジェクトに変換でバグ確認

OS X 10.8でdateまわりにバグが発生していることは衆知の事実ですが、当初は日本語などのCJK環境でのみ発生している現象かと思っていたら……海外のMLを見るかぎり、英語環境でも起こっているようです。

新たにISO形式日付とdateとの相互変換テストを行ってみたところ、「date→ISO形式日付」では問題なく処理できるのに、「ISO形式日付→date」でエラーになることを(OS X 10.8.4、10.7.5等の上で)確認しました。

datebug2.png

dateiso.png

スクリプト名:ISO形式日付に変換でバグ
– AS date -> ISO8601 format
set isoDate to ((current date) as «class isot») as string
log isoDate
–> (*2013-06-25T10:59:06*)

– ISO8601 format -> AS date
set normalDate to “1997-07-16T19:20:00″ as «class isot» as date
log normalDate
–> Error

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

いいかげん、AppleScriptも登場して20年たつので……こういう基礎的な部分でバグを出さないように強くお願いしたいところです(Mac OS X 10.3のときに「is in」でバグを出されたときにはAppleのエンジニアに殺意を抱きました)。

AppleScriptが二十歳を迎えるにあたって、この際、いままでうやむやにしてきたことをハッキリしてほしいと希望します。

とくに、「驚愕のビックリドッキリ仕様」を一部なんとかしてほしいです。

 ・ファイル入出力時の暗示的なテキストエンコーディング指定の存在を明示化
 ・10E10ごときで指数表示になってしまう、驚愕の数値型の拡張(いいかげん、AppleScriptでも扱うデータ量が増えてきているので、ものすごく具合いが悪い)
 ・ずーーっと実装されているのに、いまだに往生際悪く予約語が与えられていない「ISO日付」形式の取扱い

あたりが最優先課題でしょうか。

2013/06/25 MacBook Pro RetinaでLCDの製造メーカー名を取得する

MacBook Pro Retina Displayモデル(MacBookPro10,1/MacBookPro10,2)において……とくに、15インチモデル(MacBookPro10,1)においては液晶モニタの製造メーカーによっては焼き付き現象が起こると言われています。

この、Retina Displayの製造メーカー名を取得するAppleScriptです。

retina1.png

もちろん、Retina Display搭載Macでなければ実行しても意味はありません。一応、機種IDを取得してチェックしていますが、Retina Displayを搭載しているかどうかという情報も取得できるので、そちらも合わせてチェックしてもよいかもしれません。

スクリプト名:MacBook Pro RetinaでLCDの製造メーカー名を取得する
set machineID to getMachineID() of me
if machineID contains “MacBookPro10,” then
  set aRes to do shell script “ioreg -lw0 | grep \”EDID\” | sed \”/[^< ]*
  
set sList to paragraphs of aRes
  
set makerCode to contents of item 2 of sList
  
  
if makerCode begins with “LS” then
    display dialog “SAMSUNG (Safe)” with title “Your Retina Display Maker is…”
  else if makerCode begins with “LP” then
    display dialog “LG (Danger)” with title “Your Retina Display Maker is…”
  else
    display dialog “Unknown Maker”
  end if
  
else
  display dialog “Your machine seems not to be a Retina MacBook Pro…”
  
end if

–機種IDを取得する
on getMachineID()
  set a to do shell script “ioreg -c CPU -w 0 | head -n 2 | tail -n 1″
  
  
set sPos to offset of “< " in a
  
if sPos = 0 then return “”
  
  
set spPos to offset of “+-o” in a
  
if spPos = 0 then return “”
  
  
set cpuText to text (spPos + 3) thru (sPos - 1) of a
  
return cpuText
end getMachineID

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

2013/06/22 Finder上で選択中のフォルダをDMGに変換してZip圧縮

Finder上でフォルダを選択して、本AppleScriptをScript Menuから呼び出して実行すると、選択していたフォルダをDMG(Disk Image File)に変換して、さらにZip圧縮します。オリジナルのフォルダと途中のDMGファイルは自動で削除されます。

Finder上で選択されていたものの中にファイルが含まれていた場合には、ファイルについてはスキップします。

HDDやSSDの容量が足りない場合に、フォルダ単位でDMG化→ZIP圧縮して容量を稼ごうとするときに使うものです。しばらくつかいそうもない資料とか、やたらとファイル数があるWeb系の(古めの)コンテンツなんかをアーカイブするときに使用します。

テストを繰り返して、使ってみても大丈夫な感じではあるのですが、より広域に使用テストを行っていただいたほうがよいだろうと考えて、掲載してみました。問題があれば、お知らせください。

スクリプト名:Finder上で選択中のフォルダをDMGに変換してZip圧縮

property deleteV : true –DMG変換後に対象フォルダの削除フラグ(trueで削除)
property zipDmg : true –DMG作成後にZip圧縮するフラグ(trueで圧縮+DMG削除、falseだと何もしない)

–Finder上の選択項目を取得、選択されていない場合には処理終了
tell application “Finder”
  set selList to selection
  
if selList = {} or selList = “” or selList = missing value then
    activate
    
display dialog “何も選択されていません。” buttons {“OK”} default button 1 with icon 1
    
return
  end if
end tell

–選択されたアイテムのうちフォルダだけを抽出
if class of selList is not equal to list then
  set aList to (selList as alias) as list –Mac OS X 10.4対策
else
  set aList to {}
  
repeat with i in selList
    –選択したアイテムのうちフォルダであるものだけを処理対象とする
    
set j to (contents of i) as alias
    
set aInfo to info for j
    
if folder of aInfo = true then
      set the end of aList to j
    end if
  end repeat
end if

tell application “Finder”
  
  
set dmgList to {}
  
  
–選択中のアイテムを順次処理するループ
  
repeat with i in aList
    set the end of dmgList to makeDMGfromFolderAlias(i, “”) of me –パスワードは付けない
    
    
–処理後のフォルダを削除する処理
    
if (deleteV as boolean) = true then
      deleteFolderItself(i) of me
    else
      –元フォルダを削除しない場合、元フォルダの名前を変更する
      
tell application “Finder”
        set aName to name of i
      end tell
      
      
set aName to aName & “_origFol”
      
      
tell application “Finder”
        set name of i to aName
      end tell
    end if
  end repeat
  
  
  
–DMG作成後にZip圧縮する(trueで圧縮+DMG削除)
  
if zipDmg = true then
    repeat with i in dmgList
      set j to contents of i
      
      
set f1Path to quoted form of j –DMGファイルのPOSIX Path
      
set f2Path to quoted form of (j & “.zip”) –ZipファイルのPOSIX Path
      
      
–タイムアウト時間は2時間
      
with timeout of 7200 seconds
        do shell script “/usr/bin/zip -r -j “ & f2Path & ” “ & f1Path
        
do shell script “/bin/rm -f “ & f1Path
      end timeout
      
    end repeat
  end if
end tell

–指定のフォルダ(alias)をDiskImageに変換する
on makeDMGfromFolderAlias(aaSel, aPassword)
  
  
set aPassword to aPassword as string
  
  
tell application “Finder”
    set fn to name of aaSel
  end tell
  
  
set aDir to (POSIX path of aaSel)
  
set aDir to quoted form of aDir
  
  
set fp2 to do shell script “dirname “ & aDir
  
set fp2 to fp2 & “/”
  
  
set outPath to (fp2 & fn & “.dmg”)
  
  
if aPassword is not equal to “” then
    –パスワードつきの場合
    
set aCMD to “printf ‘” & aPassword & “‘ | hdiutil create -encryption -srcfolder “ & aDir & ” “ & (quoted form of outPath)
  else
    –パスワードなしの場合
    
set aCMD to “hdiutil create -srcfolder “ & aDir & ” “ & (quoted form of outPath)
  end if
  
  
set fp3 to do shell script aCMD
  
  
return outPath
  
end makeDMGfromFolderAlias

–指定フォルダ内をすべて削除し、そのフォルダ自体も削除
on deleteFolderItself(aFol)
  set aU to (quoted form of POSIX path of aFol)
  
try
    do shell script “rm -rf “ & aU
  end try
end deleteFolderItself

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

2013/06/20 Transmitでファイルリストをリフレッシュしてから取得

FTPソフトウェア「Transmit」で、オープン中のサーバー内のファイル/フォルダ名一覧画面の内容を、一度リフレッシュ(更新)してから一覧を取得するAppleScriptです。

FTPソフトウェアの多くで、ファイル/フォルダ名一覧を取得するときに、リフレッシュするAppleScript命令を持っています。なければ、出来損ないです。

スクリプト名:Transmitでファイルリストをリフレッシュしてから取得
tell application “Transmit”
  set dCount to count every document
  
  
if dCount = 0 then return {}
  
  
tell document 1
    tell current tab
      set rList to every file browser whose remote is true
      
set remoteBrowser to contents of first item of rList
      
      
tell remoteBrowser
        refresh
        
set bList to name of browser items
      end tell
      
    end tell
  end tell
end tell

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

2013/06/20 指定フォルダが所属するDiskの空き容量が制限値よりも多いかチェック

指定フォルダが所属するDiskの空き容量が、制限値よりも多いかどうかチェックするAppleScriptです。

finder1.png

Finder上の空き容量表示が上記のような場合に、このDisk内のフォルダを指定すると、指定の制限値と実際の空き容量を比較し、制限値よりも空きが多い場合にはtrueを、そうでない場合にはfalseを返します。

制限値については、「32G」「105M」といった単位つきの表現が使えます。Diskの空き容量の計算方法はMac OS X 10.6で変更(1K=1000)されたため、基数を1000に指定しておく必要があります。Mac OS X 10.6より古いOSで動かす場合には、基数を1024に指定してください。

スクリプト名:指定フォルダが所属するDiskの空き容量が制限値よりも多いかチェック
set aFol to choose folder
set dRes to chkFreeSpaceOfFoldersDisk(aFol, “134G”) of me
–> true

–指定フォルダが所属するDiskの空き容量が指定の制限値よりも多いかチェック
on chkFreeSpaceOfFoldersDisk(aFol, minimumDiskSpace)
  set minimumSpace to procMetricPrefix(minimumDiskSpace, 1000) of me –Mac OS X 10.6以降だと1000,それ以前だと1024を指定
  
  
set fRes to getFreeSpace(aFol) of me
  
  
considering numeric strings
    if fRes minimumSpace then
      return false –空き容量が制限値よりも少なかった
    else
      return true –多かった
    end if
  end considering
  
end chkFreeSpaceOfFoldersDisk

on getFreeSpace(aFol)
  set aFolStr to aFol as string
  
set aList to parseByDelim(aFolStr, “:”) of me
  
set aDiskName to contents of first item of aList
  
  
tell application “Finder”
    set aDisk to disk aDiskName
    
set aFree to free space of aDisk
  end tell
  
  
set numberString to Stringify(aFree) of me
  
  
return numberString
  
end getFreeSpace

–与えられた文字列を、指定デリミタ文字で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

–数字の文字だけが入っているかどうかをテストする
–数字の文字のみから構成されていたらtrue、1文字でもそれ以外のものが入っていたらfalse
on detectContainsOnlyNumChar(testText)
  –Numeric文字列(大文字小文字は問わない)
  
set ankChar to {“0″, “1″, “2″, “3″, “4″, “5″, “6″, “7″, “8″, “9″, “.”, “-”}
  
  
set _testChar to testText as Unicode text
  
  
ignoring case
    repeat with i in _testChar
      set j to contents of i
      
if j is not in ankChar then
        return false
      end if
    end repeat
  end ignoring
  
  
return true
end detectContainsOnlyNumChar

–指数表示数値を文字列化
on Stringify(x) – for E+ numbers
  set x to x as string
  
set {tids, AppleScript’s text item delimiters} to {AppleScript’s text item delimiters, {“E+”}}
  
if (count (text items of x)) = 1 then
    set AppleScript’s text item delimiters to {tids}
    
return x
  else
    set {n, z} to {text item 1 of x, (text item 2 of x) as integer}
    
set AppleScript’s text item delimiters to {tids}
    
set i to character 1 of n
    
set decSepChar to character 2 of n – “.” or “,”
    
set d to text 3 thru -1 of n
    
set l to count d
    
if l > z then
      return (i & (text 1 thru z of d) & decSepChar & (text (z + 1) thru -1 of d))
    else
      repeat (z - l) times
        set d to d & “0″
      end repeat
      
return (i & d)
    end if
  end if
end Stringify

–SI接頭辞つきの数値を具体的な数値の文字列に評価して返す
–https://en.wikipedia.org/wiki/Metric_prefix
on procMetricPrefix(numStrWithMetric, aBase)
  
  
if (aBase = 1024) or (aBase = 1000) then
    
    
set numPart to (text 1 thru -2 of numStrWithMetric) as integer
    
set aBaseStr to aBase as string
    
    
if numStrWithMetric ends with “K” then
      set multNum to 1
    else if numStrWithMetric ends with “M” then
      set multNum to 2
    else if numStrWithMetric ends with “G” then
      set multNum to 3
    else if numStrWithMetric ends with “T” then
      set multNum to 4
    else if numStrWithMetric ends with “P” then
      set multNum to 5
    else if numStrWithMetric ends with “E” then
      set multNum to 6
    else if numStrWithMetric ends with “Z” then
      set multNum to 7
    else if numStrWithMetric ends with “Y” then
      set multNum to 8
      
    else
      return false
    end if
    
    
set aCMD to “echo \” scale=10; “ & aBaseStr & “^” & (multNum as string) & “*” & numPart & “\” | bc”
    
set aRes to do shell script aCMD
    
return aRes
    
  else
    return false
    
  end if
end procMetricPrefix

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

2013/06/19 Transmitのお気に入りから、サーバーのアドレスとディレクトリ情報を取得する

FTPクライアント「Transmit」の「お気に入り」(いわゆるブックマーク)の指定名称の項目から、サーバーのアドレスとディレクトリ情報を取得するAppleScriptです。

Mac OS X FTPクライアントの最高峰との呼び声も高い、FTPソフトウェア「Transmit」。他のFTPソフトウェアを使っていた自動処理システムで(WebDAVのサーバーにアクセスしていました)、信頼性と転送速度とAppleScript対応度からTransmitに切り替えてみたところ、これらすべての面で不満が解消されました。

そんな自動処理システムで、本番サーバーとテストサーバーの切り換えを、Transmitの「お気に入り」に登録した名称で切り替えて指定していたのですが、お気に入り項目の詳細データが必要になって……それをプログラム中に直接記述するのはスマートではないため、Transmitに問い合わせて値を取得することにしました。

そんな用途に使ったのが、このAppleScriptです。

trans2.png

trans1.png

Transmitのお気に入り項目を文字で指定して、実際にお気に入り項目にアクセスし、アドレスとパスの情報を取得します。

スクリプト名:Transmitのお気に入りから、サーバーのアドレスとディレクトリ情報を取得する
set aName to "www.piyocast.com"
set {anAddr, aPath} to getFavoriteInfoByName(aName) of me
–> {"www.piyocast.com", "/piyocast.com"}

–お気に入りから、サーバーのアドレスとディレクトリ情報を取得する
on getFavoriteInfoByName(aName)
  tell application "Transmit"
    try
      set tmpFav to favorite aName
      
set aInfo to properties of tmpFav
      
set anAdr to address of aInfo
      
set anPath to remote path of aInfo
      
return {anAdr, anPath}
    on error
      return {false, false}
      
    end try
  end tell
end getFavoriteInfoByName

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

2013/06/19 指定フォルダが所属するDiskの空き容量を取得する

指定フォルダが所属するDiskの空き容量を取得するAppleScriptです。

USBメモリ上のフォルダを指定したら、そのUSBメモリの空き容量を求めます。

サーバー上のQuotaがかかった(ユーザーごとの書き込み上限容量が設定された)状態の使用可能残容量を求めるといった用途は考慮していません。

スクリプト名:指定フォルダが所属するDiskの空き容量を取得する
set aFol to choose folder

set fRes to getFreeSpace(aFol) of me

on getFreeSpace(aFol)
  set aFolStr to aFol as string
  
set aList to parseByDelim(aFolStr, “:”) of me
  
set aDiskName to contents of first item of aList
  
  
tell application “Finder”
    set aDisk to disk aDiskName
    
set aFree to free space of aDisk
  end tell
  
  
set numberString to Stringify(aFree) of me
  
  
return numberString
  
end getFreeSpace

–与えられた文字列を、指定デリミタ文字で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

–数字の文字だけが入っているかどうかをテストする
–数字の文字のみから構成されていたらtrue、1文字でもそれ以外のものが入っていたらfalse
on detectContainsOnlyNumChar(testText)
  –Numeric文字列(大文字小文字は問わない)
  
set ankChar to {“0″, “1″, “2″, “3″, “4″, “5″, “6″, “7″, “8″, “9″, “.”, “-”}
  
  
set _testChar to testText as Unicode text
  
  
ignoring case
    repeat with i in _testChar
      set j to contents of i
      
if j is not in ankChar then
        return false
      end if
    end repeat
  end ignoring
  
  
return true
end detectContainsOnlyNumChar

–指数表示数値を文字列化
on Stringify(x) – for E+ numbers
  set x to x as string
  
set {tids, AppleScript’s text item delimiters} to {AppleScript’s text item delimiters, {“E+”}}
  
if (count (text items of x)) = 1 then
    set AppleScript’s text item delimiters to {tids}
    
return x
  else
    set {n, z} to {text item 1 of x, (text item 2 of x) as integer}
    
set AppleScript’s text item delimiters to {tids}
    
set i to character 1 of n
    
set decSepChar to character 2 of n – “.” or “,”
    
set d to text 3 thru -1 of n
    
set l to count d
    
if l > z then
      return (i & (text 1 thru z of d) & decSepChar & (text (z + 1) thru -1 of d))
    else
      repeat (z - l) times
        set d to d & “0″
      end repeat
      
return (i & d)
    end if
  end if
end Stringify

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

2013/06/19 SI接頭辞つきの数値を具体的な数値の文字列に評価して返す

“M”とか”G”とかの単位を表す文字を末尾に指定した数値表現を、具体的な数字に評価して(文字列で)返すAppleScriptです。基数を1000もしくは1024で指定できます。

「ディスクの空き容量が40GB以下だったら処理しない」といった指定ができるように、作ってみました。

AppleScriptの処理系自体は、10E10ぐらいで指数表示になってしまい、大きな数値を扱うのにあまり向いていませんが……このように数値を文字として保持し、計算はbcコマンドを用いれば巨大な数値を扱えるようになります。

……いかにも、世界のどこかで誰かが作っていそうな感じではありますが(逆の働きをするものは自分でも作っていました)、すぐには見つからなかったので、仕方なく。

スクリプト名:SI接頭辞つきの数値を具体的な数値の文字列に評価して返す
set aRes to procMetricPrefix(“4K”, 1024) of me
–> “4096″

set aRes to procMetricPrefix(“4M”, 1024) of me
–> “4194304″

set aRes to procMetricPrefix(“4G”, 1024) of me
–> “4294967296″

set aRes to procMetricPrefix(“4G”, 1000) of me
–> “4000000000″

–SI接頭辞つきの数値を具体的な数値の文字列に評価して返す
–https://en.wikipedia.org/wiki/Metric_prefix
on procMetricPrefix(numStrWithMetric, aBase)
  
  
if (aBase = 1024) or (aBase = 1000) then
    
    
set numPart to (text 1 thru -2 of numStrWithMetric) as integer
    
set aBaseStr to aBase as string
    
    
if numStrWithMetric ends with “K” then
      set multNum to 1
    else if numStrWithMetric ends with “M” then
      set multNum to 2
    else if numStrWithMetric ends with “G” then
      set multNum to 3
    else if numStrWithMetric ends with “T” then
      set multNum to 4
    else if numStrWithMetric ends with “P” then
      set multNum to 5
    else if numStrWithMetric ends with “E” then
      set multNum to 6
    else if numStrWithMetric ends with “Z” then
      set multNum to 7
    else if numStrWithMetric ends with “Y” then
      set multNum to 8
      
    else
      return false
    end if
    
    
set aCMD to “echo \” scale=10; “ & aBaseStr & “^” & (multNum as string) & “*” & numPart & “\” | bc”
    
set aRes to do shell script aCMD
    
return aRes
    
  else
    return false
    
  end if
end procMetricPrefix

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

2013/06/18 1DList中の全角英数字を半角に

1DのList(1次元配列)の内容の各要素中の全角英数字を半角に変換するAppleScriptです。

stringのidを取得できるようになったMac OS X 10.5以降(だったか)でのみ動作します。

いまMac OS X 10.5の環境が身近にないので、少なくともMac OS X 10.6以降では、以前に作って掲載した「全角文字から半角文字への置換」ではなく、こちらの使用をおすすめします。

スクリプト名:1DList中の全角英数字を半角に

set aList to {“Piyomaru Software”, “0123456789”, “abcdefghijklmnopqrstuvwxyz”, “ABCDEFGHIJKLMNOPQRSTUVWXYZ”, “.”, “_”, “−”, “+”, “漢字とひらがなとカタカナはそのまま”}

set bList to zen2hanList(aList) of me
–>{”Piyomaru Software”, “0123456789″, “abcdefghijklmnopqrstuvwxyz”, “ABCDEFGHIJKLMNOPQRSTUVWXYZ”, “.”, “_”, “-”, “+”, “漢字とひらがなとカタカナはそのまま”}

–1DList中の全角英数字を半角に
on zen2hanList(aList)
  set aaList to {}
  
  
repeat with i in aList
    set j to contents of i
    
set the end of aaList to zen2han(j) of me
  end repeat
  
  
return aaList
  
end zen2hanList

–全角文字から半角文字への置換
on zen2han(aText)
  
  
–全角文字のリスト
  
–set zList to {”A”, “B”, “C”, “D”, “E”, “F”, “G”, “H”, “I”, “J”, “K”, “L”, “M”, “N”, “O”, “P”, “Q”, “R”, “S”, “T”, “U”, “V”, “W”, “X”, “Y”, “Z”, “a”, “b”, “c”, “d”, “e”, “f”, “g”, “h”, “i”, “j”, “k”, “l”, “m”, “n”, “o”, “p”, “q”, “r”, “s”, “t”, “u”, “v”, “w”, “x”, “y”, “z”, “.”, “_”, “−”, “+”, “0”, “1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9″, “ ”}
  
set zList to {65313, 65314, 65315, 65316, 65317, 65318, 65319, 65320, 65321, 65322, 65323, 65324, 65325, 65326, 65327, 65328, 65329, 65330, 65331, 65332, 65333, 65334, 65335, 65336, 65337, 65338, 65345, 65346, 65347, 65348, 65349, 65350, 65351, 65352, 65353, 65354, 65355, 65356, 65357, 65358, 65359, 65360, 65361, 65362, 65363, 65364, 65365, 65366, 65367, 65368, 65369, 65370, 65294, 65343, 8722, 65291, 65296, 65297, 65298, 65299, 65300, 65301, 65302, 65303, 65304, 65305, 12288}
  
  
–半角文字のリスト
  
–set hList to {”A”, “B”, “C”, “D”, “E”, “F”, “G”, “H”, “I”, “J”, “K”, “L”, “M”, “N”, “O”, “P”, “Q”, “R”, “S”, “T”, “U”, “V”, “W”, “X”, “Y”, “Z”, “a”, “b”, “c”, “d”, “e”, “f”, “g”, “h”, “i”, “j”, “k”, “l”, “m”, “n”, “o”, “p”, “q”, “r”, “s”, “t”, “u”, “v”, “w”, “x”, “y”, “z”, “.”, “_”, “-”, “+”, “0″, “1″, “2″, “3″, “4″, “5″, “6″, “7″, “8″, “9″, ” “}
  
set hList to {65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 46, 95, 45, 43, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 32}
  
  
–1文字ごとに分解したリストにする
  
set cList to characters of aText
  
  
  
set newList to {}
  
  
–文字列比較で大文字小文字を考慮して処理  
  
repeat with i in cList
    
    
set j to contents of i
    
set jj to id of j –stringの文字コードを取得する
    
    
if jj is not in hList then
      if jj is in zList then
        
        
–全角文字への対処
        
set aCount to 1
        
        
repeat with ii in zList
          set anID to contents of ii
          
if jj is equal to anID then
            exit repeat
          end if
          
set aCount to aCount + 1
        end repeat
        
        
set zRes to string id (contents of item aCount of hList)
        
      else
        –上記のどちらでもない場合(全角漢字、ひらがな、かたかな、記号文字、半角カタカナなど
        
set zRes to j
      end if
    else
      –半角文字の場合
      
set zRes to j
    end if
    
    
set the end of newList to zRes
    
  end repeat
  
  
return newList as string
  
end zen2han

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

2013/06/13 抽出したデータをページに着目して同一ページ同士を入れ子リストに v3

抽出した2Dリストのデータをページに着目して、同一ページのもの同士を入れ子の3Dリストに変換するものです。

前バージョンでは、説明の簡略化のためにリスト内の項目は2つだけにしておきましたが……実際には「そんなバカみたいに簡単な仕様のプログラムはない」わけで、いまひとつ実戦向きではありませんでした。

そこで、リスト項目をセットにする基準になるデータ(ページ数など)を、各リストの何項目目がそれに該当するのかを数値で指定できるようにしてみました。つまり、処理に柔軟性を持たせられるようにしたわけです。

これで、ぐっと実用性が増しましたが……SSDと速いマシンさえあればいらないような……いえ、SSDと速いマシンとこうした処理の組み合わせで、さらに高速な処理を、、、、

AppleScript名:抽出したデータをページに着目して同一ページ同士を入れ子リストに v3
–v3 リストの項目数を可変にできるようにした
–v2 本体側で行った修正を反映した

set aList to {{"AAAAAAAAAA", "038"}, {"BBBBBBBBBB", "038"}, {"CCCCCCCCCC", "055"}, {"DDDDDDDDDD", "055"}, {"EEEEEEEEEE", "056"}, {"FFFFFFFFFF", "056"}, {"GGGGGGGGGG", "169"}, {"HHHHHHHHHH", "185"}, {"IIIIIIIIII", "036"}, {"JJJJJJJJJJ", "036"}, {"KKKKKKKKKK", "036"}, {"LLLLLLLLLL", "038"}, {"MMMMMMMMMM", "163"}, {"NNNNNNNNNN", "163"}, {"OOOOOOOOOO", "163"}, {"PPPPPPPPPP", "163"}, {"QQQQQQQQQQ", "163"}, {"RRRRRRRRRR", "163"}, {"SSSSSSSSSS", "169"}, {"TTTTTTTTTT", "169"}, {"UUUUUUUUUU", "174"}, {"VVVVVVVVVV", "174"}, {"WWWWWWWWWW", "185"}, {"XXXXXXXXXX", "185"}, {"YYYYYYYYYY", "186"}, {"ZZZZZZZZZZ", "186"}, {"アアアアアアアアアア", "186"}, {"イイイイイイイイイイ", "056"}, {"ウウウウウウウウウウ", "168"}, {"エエエエエエエエエエ", "168"}, {"オオオオオオオオオオ", "168"}, {"カカカカカカカカカカ", "185"}, {"キキキキキキキキキキ", "161"}, {"クククククククククク", "161"}, {"ケケケケケケケケケケ", "161"}, {"ココココココココココ", "161"}, {"ササササササササササ", "161"}, {"シシシシシシシシシシ", "163"}, {"スススススススススス", "168"}, {"セセセセセセセセセセ", "169"}, {"ソソソソソソソソソソ", "169"}, {"タタタタタタタタタタ", "169"}, {"チチチチチチチチチチ", "170"}, {"ツツツツツツツツツツ", "170"}, {"テテテテテテテテテテ", "174"}, {"トトトトトトトトトト", "174"}, {"ナナナナナナナナナナ", "185"}, {"ニニニニニニニニニニ", "185"}, {"ヌヌヌヌヌヌヌヌヌヌ", "185"}, {"ネネネネネネネネネネ", "186"}, {"ノノノノノノノノノノ", "186"}, {"ハハハハハハハハハハ", "036"}, {"ヒヒヒヒヒヒヒヒヒヒ", "036"}, {"フフフフフフフフフフ", "036"}, {"ヘヘヘヘヘヘヘヘヘヘ", "053"}, {"ホホホホホホホホホホ", "053"}, {"ママママママママママ", "056"}, {"ミミミミミミミミミミ", "056"}, {"ムムムムムムムムムム", "056"}, {"メメメメメメメメメメ", "056"}}

set bList to splitItemIntoEachPage(aList, 2) of pageSplitKit
–> {{{"フフフフフフフフフフ", "036"}, {"JJJJJJJJJJ", "036"}, {"KKKKKKKKKK", "036"}, {"ハハハハハハハハハハ", "036"}, {"IIIIIIIIII", "036"}, {"ヒヒヒヒヒヒヒヒヒヒ", "036"}}, {{"BBBBBBBBBB", "038"}, {"AAAAAAAAAA", "038"}, {"LLLLLLLLLL", "038"}}, {{"ホホホホホホホホホホ", "053"}, {"ヘヘヘヘヘヘヘヘヘヘ", "053"}}, {{"DDDDDDDDDD", "055"}, {"CCCCCCCCCC", "055"}}, {{"EEEEEEEEEE", "056"}, {"メメメメメメメメメメ", "056"}, {"イイイイイイイイイイ", "056"}, {"ママママママママママ", "056"}, {"FFFFFFFFFF", "056"}, {"ムムムムムムムムムム", "056"}, {"ミミミミミミミミミミ", "056"}}, {{"クククククククククク", "161"}, {"ココココココココココ", "161"}, {"キキキキキキキキキキ", "161"}, {"ケケケケケケケケケケ", "161"}, {"ササササササササササ", "161"}}, {{"シシシシシシシシシシ", "163"}, {"OOOOOOOOOO", "163"}, {"MMMMMMMMMM", "163"}, {"NNNNNNNNNN", "163"}, {"PPPPPPPPPP", "163"}, {"QQQQQQQQQQ", "163"}, {"RRRRRRRRRR", "163"}}, {{"ウウウウウウウウウウ", "168"}, {"スススススススススス", "168"}, {"オオオオオオオオオオ", "168"}, {"エエエエエエエエエエ", "168"}}, {{"TTTTTTTTTT", "169"}, {"GGGGGGGGGG", "169"}, {"SSSSSSSSSS", "169"}, {"セセセセセセセセセセ", "169"}, {"ソソソソソソソソソソ", "169"}, {"タタタタタタタタタタ", "169"}}, {{"ツツツツツツツツツツ", "170"}, {"チチチチチチチチチチ", "170"}}, {{"テテテテテテテテテテ", "174"}, {"UUUUUUUUUU", "174"}, {"VVVVVVVVVV", "174"}, {"トトトトトトトトトト", "174"}}, {{"WWWWWWWWWW", "185"}, {"XXXXXXXXXX", "185"}, {"カカカカカカカカカカ", "185"}, {"ニニニニニニニニニニ", "185"}, {"ヌヌヌヌヌヌヌヌヌヌ", "185"}, {"HHHHHHHHHH", "185"}, {"ナナナナナナナナナナ", "185"}}, {{"YYYYYYYYYY", "186"}, {"アアアアアアアアアア", "186"}, {"ネネネネネネネネネネ", "186"}, {"ノノノノノノノノノノ", "186"}, {"ZZZZZZZZZZ", "186"}}}

–リストで与えられたデータをページ数(item 2)に着目してグループ化
script pageSplitKit
  
  
on splitItemIntoEachPage(aList, keyItem)
    
    
script splitEachPage
      property aaList : {}
      
property bbList : {}
      
property ccList : {}
    end script
    
    
–初期化
    
set aaList of splitEachPage to aList
    
set bbList of splitEachPage to {} –ページごとのバッファリング先
    
set ccList of splitEachPage to {} –結果出力先    
    
    
–###################################################
    
–とりあえず与えられたリストがカラでないかを確認
    
if aList = {} or aList = "" then return false
    
    
–アイテム数のチェック(常識的な範囲での最低限のエラーチェック)
    
–与えられた2Dimension Listの各アイテムがすべて同じアイテム数であることが処理の前提条件
    
set testItem to contents of some item of aList –乱数(some item)で指定した要素をピックアップ
    
set testItemCount to count every item of testItem
    
if testItemCount < keyItem then
      return false –リスト内の項目数よりもキー項目の項目番号が大きい場合にはエラー
    end if
    
if keyItem < 1 then return false –アイテム番号が1より小さい場合にはエラー
    
–エラーチェックここまで
    
–###################################################
    
    
–ページ(各リストのキー指定アイテム)番号に着目して昇順ソート(これをやらないとひどい目に遭う!!)
    
set aaList of splitEachPage to shellSortListAscending(aaList of splitEachPage, keyItem) of me
    
    
    
set bbList of splitEachPage to {} –ページごとのバッファリング先
    
set ccList of splitEachPage to {} –結果出力先
    
    
set curPage to 0
    
    
repeat with i in aaList of splitEachPage
      set j to contents of i
      
      
set j2 to contents of item keyItem of j –アイテム数を変更しても大丈夫なようにした
      
      
if curPage = 0 then
        –最初のデータ処理時
        
set bbList of splitEachPage to {j} –バッファにデータを入れる
        
set curPage to j2
        
      else if curPage = j2 then
        –同一ページのデータを処理中の場合
        
set the end of bbList of splitEachPage to j –バッファにデータを追加
        
      else if curPage is not equal to j2 then
        –ページ境界を検出した(ページが変更になった)場合
        
if bbList of splitEachPage = {} then
          –バッファしているものがなかったとき
          
set the end of ccList of splitEachPage to {j}
        else
          –バッファ中のものが存在していたとき
          
set the end of ccList of splitEachPage to contents of bbList of splitEachPage
        end if
        
        
set bbList of splitEachPage to {j} –バッファにデータを入れる
        
set curPage to j2 –現在処理中のページを変更する
        
      end if
    end repeat
    
    
–この手の処理で忘れてはいけない。バッファに入っていた最終データの結果への出力
    
if bbList of splitEachPage is not equal to {} then
      set the end of ccList of splitEachPage to contents of bbList of splitEachPage
    end if
    
    
    
    
return ccList of splitEachPage
    
  end splitItemIntoEachPage
  
  
  
–シェルソートで入れ子のリストを昇順ソート
  
on shellSortListAscending(aSortList, aKeyItem)
    script oBj
      property list : aSortList
    end script
    
set len to count oBj’s list’s items
    
set gap to 1
    
repeat while (gap len)
      set gap to ((gap * 3) + 1)
    end repeat
    
repeat while (gap > 0)
      set gap to (gap div 3)
      
if (gap < len) then
        repeat with i from gap to (len - 1)
          set temp to oBj’s list’s item (i + 1)
          
set j to i
          
repeat while ((j gap) and (contents of item aKeyItem of (oBj’s list’s item (j - gap + 1)) > item aKeyItem of temp))
            set oBj’s list’s item (j + 1) to oBj’s list’s item (j - gap + 1)
            
set j to j - gap
          end repeat
          
set oBj’s list’s item (j + 1) to temp
        end repeat
      end if
    end repeat
    
return oBj’s list
  end shellSortListAscending
  
end script

★Click Here to Open This Script 

2013/06/13 抽出したデータをページに着目して同一ページ同士を入れ子リストに v2

2Dリストで保持している情報を、アイテム中の「ページ数」に着目して、同一ページのもの同士を組みにした入れ子の3Dリストに変換するものです。

これは……データ処理を圧倒的に高速化するためのもので、たとえばInDesignの書類(1書類に1ページ)が大量にあって、各書類から複数のデータを抽出しなければならないようなプログラムを作ろうとしたとします。

抽出対象のデータの情報はExcelなどで提供されており、このExcel書類上から抽出対象の情報を抜き出し、それを元にInDesign書類をオープンして、必要な情報を取り出すことを考えると……InDesign書類のオープンに時間がかかります。そのため、各データの抽出時にいちいち毎回InDesign書類をオープン/クローズしていては、やたらと時間がかかります。InDesign書類のオープンを最低限にすることが重要です。

同じページ、同じ書類上から複数のデータを抽出するようにして、同じInDesign書類の複数回のオープンを防ぐことができれば、処理は最低限の時間で済むはずです。

そのため、Excelから抽出対象のデータを取得したら、本Scriptのような処理で「同一ページ=同一書類上に存在しているデータ同士を組みにする」ようにする必要があります。本AppleScriptはそのためのものです。

何も考えずにScriptを組み出すと、抽出データの数だけInDesign書類をオープン/クローズするようなプログラムになりますが、このようにていねいに前処理を行うことで、圧倒的な高速処理が可能になります。

ただ、SSD上にデータを置いて高速なマシンで処理すれば、アホな処理手順でも速いわけで……新型Mac Proとか……

AppleScript名:抽出したデータをページに着目して同一ページ同士を入れ子リストに v2
–v2 本体側で行った修正を反映した

set aList to {{"AAAAAAAAAA", "038"}, {"BBBBBBBBBB", "038"}, {"CCCCCCCCCC", "055"}, {"DDDDDDDDDD", "055"}, {"EEEEEEEEEE", "056"}, {"FFFFFFFFFF", "056"}, {"GGGGGGGGGG", "169"}, {"HHHHHHHHHH", "185"}, {"IIIIIIIIII", "036"}, {"JJJJJJJJJJ", "036"}, {"KKKKKKKKKK", "036"}, {"LLLLLLLLLL", "038"}, {"MMMMMMMMMM", "163"}, {"NNNNNNNNNN", "163"}, {"OOOOOOOOOO", "163"}, {"PPPPPPPPPP", "163"}, {"QQQQQQQQQQ", "163"}, {"RRRRRRRRRR", "163"}, {"SSSSSSSSSS", "169"}, {"TTTTTTTTTT", "169"}, {"UUUUUUUUUU", "174"}, {"VVVVVVVVVV", "174"}, {"WWWWWWWWWW", "185"}, {"XXXXXXXXXX", "185"}, {"YYYYYYYYYY", "186"}, {"ZZZZZZZZZZ", "186"}, {"アアアアアアアアアア", "186"}, {"イイイイイイイイイイ", "056"}, {"ウウウウウウウウウウ", "168"}, {"エエエエエエエエエエ", "168"}, {"オオオオオオオオオオ", "168"}, {"カカカカカカカカカカ", "185"}, {"キキキキキキキキキキ", "161"}, {"クククククククククク", "161"}, {"ケケケケケケケケケケ", "161"}, {"ココココココココココ", "161"}, {"ササササササササササ", "161"}, {"シシシシシシシシシシ", "163"}, {"スススススススススス", "168"}, {"セセセセセセセセセセ", "169"}, {"ソソソソソソソソソソ", "169"}, {"タタタタタタタタタタ", "169"}, {"チチチチチチチチチチ", "170"}, {"ツツツツツツツツツツ", "170"}, {"テテテテテテテテテテ", "174"}, {"トトトトトトトトトト", "174"}, {"ナナナナナナナナナナ", "185"}, {"ニニニニニニニニニニ", "185"}, {"ヌヌヌヌヌヌヌヌヌヌ", "185"}, {"ネネネネネネネネネネ", "186"}, {"ノノノノノノノノノノ", "186"}, {"ハハハハハハハハハハ", "036"}, {"ヒヒヒヒヒヒヒヒヒヒ", "036"}, {"フフフフフフフフフフ", "036"}, {"ヘヘヘヘヘヘヘヘヘヘ", "053"}, {"ホホホホホホホホホホ", "053"}, {"ママママママママママ", "056"}, {"ミミミミミミミミミミ", "056"}, {"ムムムムムムムムムム", "056"}, {"メメメメメメメメメメ", "056"}}

set bList to splitItemIntoEachPage(aList) of pageSplitKit
–> {{{"フフフフフフフフフフ", "036"}, {"JJJJJJJJJJ", "036"}, {"KKKKKKKKKK", "036"}, {"ハハハハハハハハハハ", "036"}, {"IIIIIIIIII", "036"}, {"ヒヒヒヒヒヒヒヒヒヒ", "036"}}, {{"BBBBBBBBBB", "038"}, {"AAAAAAAAAA", "038"}, {"LLLLLLLLLL", "038"}}, {{"ホホホホホホホホホホ", "053"}, {"ヘヘヘヘヘヘヘヘヘヘ", "053"}}, {{"DDDDDDDDDD", "055"}, {"CCCCCCCCCC", "055"}}, {{"EEEEEEEEEE", "056"}, {"メメメメメメメメメメ", "056"}, {"イイイイイイイイイイ", "056"}, {"ママママママママママ", "056"}, {"FFFFFFFFFF", "056"}, {"ムムムムムムムムムム", "056"}, {"ミミミミミミミミミミ", "056"}}, {{"クククククククククク", "161"}, {"ココココココココココ", "161"}, {"キキキキキキキキキキ", "161"}, {"ケケケケケケケケケケ", "161"}, {"ササササササササササ", "161"}}, {{"シシシシシシシシシシ", "163"}, {"OOOOOOOOOO", "163"}, {"MMMMMMMMMM", "163"}, {"NNNNNNNNNN", "163"}, {"PPPPPPPPPP", "163"}, {"QQQQQQQQQQ", "163"}, {"RRRRRRRRRR", "163"}}, {{"ウウウウウウウウウウ", "168"}, {"スススススススススス", "168"}, {"オオオオオオオオオオ", "168"}, {"エエエエエエエエエエ", "168"}}, {{"TTTTTTTTTT", "169"}, {"GGGGGGGGGG", "169"}, {"SSSSSSSSSS", "169"}, {"セセセセセセセセセセ", "169"}, {"ソソソソソソソソソソ", "169"}, {"タタタタタタタタタタ", "169"}}, {{"ツツツツツツツツツツ", "170"}, {"チチチチチチチチチチ", "170"}}, {{"テテテテテテテテテテ", "174"}, {"UUUUUUUUUU", "174"}, {"VVVVVVVVVV", "174"}, {"トトトトトトトトトト", "174"}}, {{"WWWWWWWWWW", "185"}, {"XXXXXXXXXX", "185"}, {"カカカカカカカカカカ", "185"}, {"ニニニニニニニニニニ", "185"}, {"ヌヌヌヌヌヌヌヌヌヌ", "185"}, {"HHHHHHHHHH", "185"}, {"ナナナナナナナナナナ", "185"}}, {{"YYYYYYYYYY", "186"}, {"アアアアアアアアアア", "186"}, {"ネネネネネネネネネネ", "186"}, {"ノノノノノノノノノノ", "186"}, {"ZZZZZZZZZZ", "186"}}}

–リストで与えられたデータをページ数(item 2)に着目してグループ化
script pageSplitKit
  
  
on splitItemIntoEachPage(aList)
    
    
script splitEachPage
      property aaList : {}
      
property bbList : {}
      
property ccList : {}
    end script
    
    
    
set aaList of splitEachPage to aList
    
    
–ページ(各リストの2番目のアイテム)番号に着目して昇順ソート
    
set aaList of splitEachPage to shellSortListAscending(aaList of splitEachPage, 2) of me
    
    
    
set bbList of splitEachPage to {} –ページごとのバッファリング先
    
set ccList of splitEachPage to {} –結果出力先
    
    
set curPage to 0
    
    
repeat with i in aaList of splitEachPage
      set j to contents of i
      
      
set {j1, j2} to j –もともとは数十項目もあるデータだったが、説明のためシンプルに2項目にした
      
      
if curPage = 0 then
        –最初の処理時
        
set the end of bbList of splitEachPage to j
        
set curPage to j2
        
      else if curPage = j2 then
        set the end of bbList of splitEachPage to j
        
      else if curPage is not equal to j2 then
        set curPage to j2
        
        
if bbList of splitEachPage = {} then
          set the end of ccList of splitEachPage to {j}
        else
          set the end of ccList of splitEachPage to contents of bbList of splitEachPage
        end if
        
        
set bbList of splitEachPage to {j}
        
      end if
    end repeat
    
    
–この手の処理で忘れてはいけない。バッファに入っていた最終データの結果への出力
    
if bbList of splitEachPage is not equal to {} then
      set the end of ccList of splitEachPage to contents of bbList of splitEachPage
    end if
    
    
    
return ccList of splitEachPage
    
  end splitItemIntoEachPage
  
  
  
–シェルソートで入れ子のリストを昇順ソート
  
on shellSortListAscending(aSortList, aKeyItem)
    script oBj
      property list : aSortList
    end script
    
set len to count oBj’s list’s items
    
set gap to 1
    
repeat while (gap len)
      set gap to ((gap * 3) + 1)
    end repeat
    
repeat while (gap > 0)
      set gap to (gap div 3)
      
if (gap < len) then
        repeat with i from gap to (len - 1)
          set temp to oBj’s list’s item (i + 1)
          
set j to i
          
repeat while ((j gap) and (contents of item aKeyItem of (oBj’s list’s item (j - gap + 1)) > item aKeyItem of temp))
            set oBj’s list’s item (j + 1) to oBj’s list’s item (j - gap + 1)
            
set j to j - gap
          end repeat
          
set oBj’s list’s item (j + 1) to temp
        end repeat
      end if
    end repeat
    
return oBj’s list
  end shellSortListAscending
  
end script

★Click Here to Open This Script 

2013/06/13 リストにバグ?

Twitterでたまたま、AppleScriptのバグに関する情報を拾いました。

listbug0.png

こういう書き方は……プログラムを正しく動くように書くためには行わない種類のもの。

{1,2}のリストの末尾に{1,2}を追加して{1,2,{1,2}}にするというのは、常識的に考えればほとんど行わない処理です。

スクリプト名:リストのバグ
set aList to {1, 2}

set the end of aList to aList

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

ためしに書いて実行してみたところ……OS X 10.8.4でAppleScriptエディタがクラッシュ!!

ほかのバージョンのOS Xで調べてみたところ、

(A)実行からもどって来ないが、AppleScriptエディタの「停止」ボタンで実行を止められる
(B)AppleScriptエディタがクラッシュする

の2つのパターンに分けられました。10.6以降が(B)、10.6よりも前だと(A)の結果になります。

listbug1.png

リストのバグなのか、AppleScriptの処理系そのもののバグなのか、あるいはAppleScriptエディタのバグなのか……Twitterで紹介していた方は、どうやら処理系そのもののバグだと言っているようです。

ためしに、Mac OSエミュレーター「Sheep Shaver」でClassic Mac OS 8.6を起動し、AppleScriptバージョンJ1-1.3.7+スクリプトエディタ J1-1.1.3上で試してみたところ……

listbug2.png

おかしな結果が帰ってきました。

これが、どのレベルで問題を起こしているのか分りませんが、さすがにAppleScriptエディタが問答無用でクラッシュするというのはいかがなものかと。

2013/06/13 WWDCでOS X 10.9, Mavericksが発表に

2日ほど前、WWDC2013でOS Xの新バージョン「10.9, Mavericks」が発表されました。

AppleScriptについてはKeynote Speechで何か発表されることはありませんが、すでに20年近い歴史を持つ(1993〜1994にリリースされたので)仕組みであり、盤石の体制に…………なっているといいんですけれど、10.8では幾つか不可思議なバグがあり……10.9でいくつかは修正されるようです。

AppleScriptObjCまわりでは、Xcode上でまともに書けるようになってほしいとか、|document| とかのAPI名とAppleScript予約語が衝突している際の泥くさい回避策とかしなくて済むようにしてもらえないかとか、願ってやまないところです。

そのほかにも、10.9でいろいろ本質的かつ有意義なAppleScript系の新機能も実装されるようなので、かなり期待しています。

個人的には、実物をさわっているのでこれ以上は何も書けません。スクリーンキャプチャも載せられません。公式に発表になったら、リンクで紹介するぐらいでしょうか。

2013/06/08 AppleScriptからTwitter投稿を

AppleScriptからTwitterに投稿する手段はいくつかあり、それぞれに一長一短あります。

(1)Twitter公式アプリをGUI Scripting経由でコントロール
Twitter公式アプリコントロールする方法です。確実といえば確実ですが、GUI Scripting経由で無理矢理コントロールするので、Twitter投稿時にTwitter.appが最前面に出てくるなど、使える場合と使いにくい場合(表示されるのがイヤ、など)があります。

(2)AppleScriptで記述されたTwitter投稿kitを使う
AppleScriptだけでAOuthなどひととおりのプログラムを書いたJAComputingの「ASTwitterLibrary」が存在しており、AppleScriptだけでTwitter投稿できる……はずなんですが、これ、URL Access ScriptingとKeychain Scriptingを必要とするらしく、この仕様だとMac OS X 10.6以下でしか動きません。

Keychain ScriptingについてはRed Sweater Softwareが「Usable Keychain Scripting For Lion」を配布しており、これで補完はできそうです。ただ……「ASTwitterLibrary」も「Usable Keychain Scripting For Lion」もいまはソース非開示でメンテナンスされているため、間違いがあっても修正しにくいし、(ASTwitterLibraryは)最新のOSバージョンにキャッチアップしていないので、応援もしにくいです。

(3)コマンドラインから使うTwitterクライアント「tw」
コマンドラインから使うTwitterクライアント「tw」を試してみたところ、なかなかよかったので、AppleScriptのdo shell scriptコマンドから呼び出してみたら問題なくTwitter投稿できました。

このtwを利用する方法はかなりいいと思うものの、準備が必要です。このあたり、コマンドラインからの操作に慣れているかどうかで利用できるかどうかが決まるでしょう。

twのインストール手順はそこかしこで紹介されていますが、(OS X 10.8.4上で確認)簡単に説明すると……コマンドラインで、

$ sudo gem update –system
$ sudo gem install tw

とするだけで、インストールできます(管理者パスワードの入力が必要)。

インストール後、コマンドライン(Terminal上)で、

$ tw hello

などとテスト投稿を行ってみると、

tw1.png

TwitterのWebサイトがWebブラウザ上でオープンして、Twitter連携の承認を求められます。これを承認すると、

tw2.png

7桁の数字(PIN)が表示され、これをTerminal上に打ち込みます

tw4.jpg

これで、準備完了です。

あとは、こんなシンプルなAppleScriptでTwitter投稿できるようになります。日本語を含む文字列であっても投稿できるし、バックグラウンドで処理できるので、なかなかいい感じです。

スクリプト名:コマンドラインから使えるTwitterクライアント経由でTwitter投稿
set aString to “てすと”
set sText to “/bin/echo ‘” & aString & “‘ | /usr/bin/tw –pipe”
do shell script sText

–>
(*
“てすと
http://twitter.com/Piyomaru/status/342889354278088704
Fri Jun 07 15:22:47 +0900 2013″
*)

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

tw3.png

▲上記AppleScriptを使ってTwitter投稿テストを行ったところ

2013/06/01 USTREAMの番組を配信 #2

本Blogの使い方をかんたんに説明する番組の第2回を配信しました。

ust2.jpg

↑画像クリックで配信ページへ

今回の内容は、「掲載Scriptのうち、使う部分と要らない部分について」です。

ちなみに、撮影はiPad 3のFaceCameraで行い、Mobiola WebCameraを使ってMacのUstream ProducerにWiFi経由で転送しています。

ust3.png
▲Mobiola WebCameraで撮影

img_1771.JPG
▲撮影はiPad 3のFaceCameraで行い、映像配信はデモ実行用のMacBook Proで

img_1772.JPG
▲配信中の内容をMacBook Airでモニタ

スクリプト名:番組で作ったScript
–ここからテスト用部分
set n to 3 –パラメータ指定部分
sayHello(n) of me
–テスト用部分ここまで

–以下が本体
on sayHello(repeatTimes)
  repeat repeatTimes times
    say “こんにちは” using “Kyoko” –音声指定部分をあとから足した
  end repeat
end sayHello

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

2013/06/01 指定フォルダ以下のファイル名をすべて取得してソートしてテキスト化して返す

指定フォルダ以下のファイルをすべて取得して、ターゲットの拡張子を持つもののファイル名だけを抽出し、昇順ソートしてテキストにして返すAppleScriptです。

この手のAppleScriptは作り捨てしているので、何回か同じようなものを掲載しているかもしれません。それだけ、よく使う種類のものです。

指定フォルダ以下の特定の拡張子のファイルを求めるには、

(1)mdfindコマンド(spotlight)を使う
(2)Finderに対してentire contentsを求めてあとで条件に基づいて抽出する
(3)shell command(lsを再帰オプション付きで)実行

などの方法があり、用途に応じて使い分けています。

実行するマシンのDiskが高速なSSDだったので(MacBook Pro Retina)、今回はテストをかねて(2)を使いました。

こういう用途だと、一般的な選択は(1)です。ただし、検索対象となるDiskがネットワーク上の特殊な(Mac OS Xで稼働しているコンピュータではない)ものだったりすると、Spotlightによる検索が利かない可能性もあるため、そういうケースでは(1)は使えません。

このため、あまりシビアな用途ではない場面において、(1)以外の「手口」についても感触を試しておく必要があります。今回はそういうケースでした。

Finderの「entire contents」は、きわめて強力な機能なのですが……返ってくるデータ量がきわめて多いのと、そのままでは返ってきたデータをFinderのオブジェクト形式に変換するのに時間がかかるため、かなり昔(Classic Mac OS時代)から存在していたにも関わらず、実用性はいまひとつでした。実用性が出てきたのは、PowerPC G5が登場してきたあたりで……かつ、「as alias list」によって返り値をaliasのlistにcastできるようになったMac OS X 10.5以降と言って差し支えないと思います。

一応、Mac OS X 10.5以前にもentire contnetsの返り値をすべて文字列化することで高速に取得し、あとで文字列をparseするという高等テクニックが存在していたのですが、「as alias list」が使えるようになると、わざわざそんなことをしなくても済むようになりました。

抽出対象のファイル数が数万ファイルや数十万ファイルになる場合には(3)を使ったほうが安全に処理できるでしょう(ただ、shell経由で処理した後でalias形式に変換するなどの処理は発生します)。

スクリプト名:指定フォルダ以下のファイル名をすべて取得してソートしてテキスト化して返す
script spd
  property aList : {}
  
property bList : {}
  
property tOut : “”
end script

property targetFileExt : “.pdf” –取得するファイル名の拡張子

set aList of spd to {}
set bList of spd to {}
set tOut of spd to “”

set a to choose folder

tell application “Finder”
  
  
–entire contentsで指定フォルダ以下のファイル/フォルダをすべて取得
  
set aList of spd to entire contents of a as alias list
  
  
repeat with i in aList of spd
    set j to contents of i
    
set aName to name of j
    
    
–ファイル抽出
    
if aName ends with targetFileExt then
      set the end of bList of spd to aName
    end if
    
  end repeat
  
end tell

set aRes to shellSortAscending(bList of spd) of me –Sort Filename by name

set tOut of spd to “”
repeat with i in aRes
  set tOut of spd to tOut of spd & (i as string) & return
end repeat

return contents of tOut of spd

–入れ子ではないリストの昇順ソート
on shellSortAscending(aSortList)
  script oBj
    property list : aSortList
  end script
  
set len to count oBj’s list’s items
  
set gap to 1
  
repeat while (gap len)
    set gap to ((gap * 3) + 1)
  end repeat
  
repeat while (gap > 0)
    set gap to (gap div 3)
    
if (gap < len) then
      repeat with i from gap to (len - 1)
        set temp to oBj’s list’s item (i + 1)
        
set j to i
        
repeat while ((j gap) and (oBj’s list’s item (j - gap + 1) > temp))
          set oBj’s list’s item (j + 1) to oBj’s list’s item (j - gap + 1)
          
set j to j - gap
        end repeat
        
set oBj’s list’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return oBj’s list
end shellSortAscending

–入れ子ではないリストの降順ソート
on shellSortDescending(aSortList)
  script oBj
    property list : aSortList
  end script
  
set len to count oBj’s list’s items
  
set gap to 1
  
repeat while (gap len)
    set gap to ((gap * 3) + 1)
  end repeat
  
repeat while (gap > 0)
    set gap to (gap div 3)
    
if (gap < len) then
      repeat with i from gap to (len - 1)
        set temp to oBj’s list’s item (i + 1)
        
set j to i
        
repeat while ((j gap) and (oBj’s list’s item (j - gap + 1) < temp))
          set oBj’s list’s item (j + 1) to oBj’s list’s item (j - gap + 1)
          
set j to j - gap
        end repeat
        
set oBj’s list’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return oBj’s list
end shellSortDescending

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