Archive for the 'ASObjCExtras' Category

2015/07/10 ASOCでDict書き込み_2

アーケードゲーム「戦場の絆」では、PC/携帯電話などで会員ページにログインすると、各MSの出撃回数を調べられるようになっています。この出撃回数を取得するAppleScriptで、出撃回数ランキングの中に出撃回数が加算されたMSについて、「▲」表記を行う処理を作成してみました。

ms1.png

ms2.png

■1位 近 装甲強化型ジム COST: 200 = 66回
▲2位 近 ザクII(F2) COST: 160 = 50回
■3位 遠 ジム・キャノン COST: 160 = 43回
■4位 近 ジム・コマンド COST: 200 = 32回
■5位 近 ジム(WD隊) COST: 160 = 28回
■6位 近 陸戦型ガンダム COST: 220 = 24回
■7位 近 ジム改 COST: 240 = 22回
■7位 遠 ガンタンク COST: 200 = 22回
■9位 格 ジム(指揮官機) COST: 160 = 20回
■10位 近 ジム COST: 120 = 19回
■11位 遠 量産型ガンタンク COST: 160 = 14回
■12位 格 陸戦型ジム COST: 120 = 12回
■13位 格 ガンダム COST: 280 = 11回
■14位 近 ジム・トレーナー COST: 120 = 9回
■14位 射 ジム・スナイパーII(WD隊) COST: 220 = 9回
■16位 射 陸戦型ガンダム(ジム頭) COST: 200 = 7回
■17位 格 ガンダムEz8 COST: 240 = 6回
■17位 近 ジム・寒冷地仕様 COST: 200 = 6回
■17位 狙 ジム・スナイパーカスタム COST: 200 = 6回
■20位 格 ジム・ストライカー COST: 180 = 4回
■21位 格 ガンキャノン重装型 COST: 160 = 3回
■22位 近 アクア・ジム COST: 160 = 2回
■22位 射 ガンキャノン COST: 200 = 2回
■24位 近 ジム・コマンドライトアーマー COST: 160 = 1回

■25位 格 ボールK型 COST: 120 = 0回
■25位 格 B.D.2号機 COST: 260 = 0回
■25位 格 プロトタイプガンダム COST: 280 = 0回
■25位 近 パワード・ジム COST: 240 = 0回
■25位 射 デザート・ジム COST: 160 = 0回
■25位 遠 量産型ガンキャノン COST: 200 = 0回

このScriptの中で、AppleScriptのrecordをplistとして保存するルーチンを作成したので紹介します。そのまま実行すると、

~/Library/Application Support/戦場の絆/

ディレクトリ内にefsf.plistというplistファイルを作成します。

plist1.png

フォルダが存在しない場合には作成します。

plist2.png

いったん、かなりの数のサブルーチンをASObjC Extras.framework用に書いたので、なかなかBridgePlusライブラリ用に書き換えるということはできないところですが、このあたりの切り替えを柔軟に行うためにも自作のScriptの機能をライブラリ化しておくとよいのかもしれません。

ただ、AppleScript系の開発で毎回ターゲットOSがOS X 10.10.xになっているわけでもなくて、少し前のバージョンだったり(10.8とか)することもあるので、なかなかライブラリ化に踏み切れないところもあります。

AppleScript名:ASOCでDict書き込み_2
use AppleScript version “2.4″
use framework “Foundation”
use framework “ASObjCExtras”
use scripting additions

set a1List to {“msName”, “sortieTimes”}
set b1List to {{“近 装甲強化型ジム 獲得済 COST: 200″, 66}, {“遠 ジム・キャノン 獲得済 COST: 160″, 43}, {“近 ザクII(F2) 獲得済 COST: 160″, 42}, {“近 ジム・コマンド 獲得済 COST: 200″, 32}, {“近 ジム(WD隊) 獲得済 COST: 160″, 28}, {“近 陸戦型ガンダム 獲得済 COST: 220″, 24}, {“近 ジム改 獲得済 COST: 240″, 22}, {“遠 ガンタンク 獲得済 COST: 200″, 22}, {“格 ジム(指揮官機) 獲得済 COST: 160″, 20}, {“近 ジム 獲得済 COST: 120″, 19}, {“遠 量産型ガンタンク 獲得済 COST: 160″, 14}, {“格 陸戦型ジム 獲得済 COST: 120″, 12}, {“格 ガンダム 獲得済 COST: 280″, 11}, {“近 ジム・トレーナー 獲得済 COST: 120″, 9}, {“射 ジム・スナイパーII(WD隊) 獲得済 COST: 220″, 9}, {“射 陸戦型ガンダム(ジム頭) 獲得済 COST: 200″, 7}, {“格 ガンダムEz8 獲得済 COST: 240″, 6}, {“近 ジム・寒冷地仕様 獲得済 COST: 200″, 6}, {“狙 ジム・スナイパーカスタム 獲得済 COST: 200″, 6}, {“格 ジム・ストライカー 獲得済 COST: 180″, 4}, {“格 ガンキャノン重装型 獲得済 COST: 160″, 3}, {“近 アクア・ジム 獲得済 COST: 160″, 2}, {“射 ガンキャノン 獲得済 COST: 200″, 2}, {“近 ジム・コマンドライトアーマー 獲得済 COST: 160″, 1}, {“格 ボールK型 獲得済 COST: 120″, 0}, {“格 B.D.2号機 獲得済 COST: 260″, 0}, {“格 プロトタイプガンダム 獲得済 COST: 280″, 0}, {“近 パワード・ジム 獲得済 COST: 240″, 0}, {“射 デザート・ジム 獲得済 COST: 160″, 0}, {“遠 量産型ガンキャノン 獲得済 COST: 200″, 0}}

set aArray to current application’s SMSFord’s subarraysIn:b1List asDictionariesUsingLabels:a1List |error|:(missing value)
set b1List to aArray’s ASify() as list

–>  {​​​​​{​​​​​​​sortieTimes:66, ​​​​​​​msName:”近 装甲強化型ジム 獲得済 COST: 200″​​​​​}, ​​​​​{​​​​​​​sortieTimes:43, ​​​​​​​msName:”遠 ジム・キャノン 獲得済 COST: 160″​​​​​}, ​​​​​{​​​​​​​sortieTimes:42, ​​​​​​​msName:”近 ザクII(F2) 獲得済 COST: 160″​​​​​}, ​​​​​{​​​​​​​sortieTimes:32, ​​​​​​​msName:”近 ジム・コマンド 獲得済 COST: 200″​​​​​}, ​​​​​{​​​​​​​sortieTimes:28, ​​​​​​​msName:”近 ジム(WD隊) 獲得済 COST: 160″​​​​​}, ​​​​​{​​​​​​​sortieTimes:24, ​​​​​​​msName:”近 陸戦型ガンダム 獲得済 COST: 220″​​​​​}, ​​​​​{​​​​​​​sortieTimes:22, ​​​​​​​msName:”近 ジム改 獲得済 COST: 240″​​​​​}, ​​​​​{​​​​​​​sortieTimes:22, ​​​​​​​msName:”遠 ガンタンク 獲得済 COST: 200″​​​​​}, ​​​​​{​​​​​​​sortieTimes:20, ​​​​​​​msName:”格 ジム(指揮官機) 獲得済 COST: 160″​​​​​}, ​​​​​{​​​​​​​sortieTimes:19, ​​​​​​​msName:”近 ジム 獲得済 COST: 120″​​​​​}, ​​​​​{​​​​​​​sortieTimes:14, ​​​​​​​msName:”遠 量産型ガンタンク 獲得済 COST: 160″​​​​​}, ​​​​​{​​​​​​​sortieTimes:12, ​​​​​​​msName:”格 陸戦型ジム 獲得済 COST: 120″​​​​​}, ​​​​​{​​​​​​​sortieTimes:11, ​​​​​​​msName:”格 ガンダム 獲得済 COST: 280″​​​​​}, ​​​​​{​​​​​​​sortieTimes:9, ​​​​​​​msName:”近 ジム・トレーナー 獲得済 COST: 120″​​​​​}, ​​​​​{​​​​​​​sortieTimes:9, ​​​​​​​msName:”射 ジム・スナイパーII(WD隊) 獲得済 COST: 220″​​​​​}, ​​​​​{​​​​​​​sortieTimes:7, ​​​​​​​msName:”射 陸戦型ガンダム(ジム頭) 獲得済 COST: 200″​​​​​}, ​​​​​{​​​​​​​sortieTimes:6, ​​​​​​​msName:”格 ガンダムEz8 獲得済 COST: 240″​​​​​}, ​​​​​{​​​​​​​sortieTimes:6, ​​​​​​​msName:”近 ジム・寒冷地仕様 獲得済 COST: 200″​​​​​}, ​​​​​{​​​​​​​sortieTimes:6, ​​​​​​​msName:”狙 ジム・スナイパーカスタム 獲得済 COST: 200″​​​​​}, ​​​​​{​​​​​​​sortieTimes:4, ​​​​​​​msName:”格 ジム・ストライカー 獲得済 COST: 180″​​​​​}, ​​​​​{​​​​​​​sortieTimes:3, ​​​​​​​msName:”格 ガンキャノン重装型 獲得済 COST: 160″​​​​​}, ​​​​​{​​​​​​​sortieTimes:2, ​​​​​​​msName:”近 アクア・ジム 獲得済 COST: 160″​​​​​}, ​​​​​{​​​​​​​sortieTimes:2, ​​​​​​​msName:”射 ガンキャノン 獲得済 COST: 200″​​​​​}, ​​​​​{​​​​​​​sortieTimes:1, ​​​​​​​msName:”近 ジム・コマンドライトアーマー 獲得済 COST: 160″​​​​​}, ​​​​​{​​​​​​​sortieTimes:0, ​​​​​​​msName:”格 ボールK型 獲得済 COST: 120″​​​​​}, ​​​​​{​​​​​​​sortieTimes:0, ​​​​​​​msName:”格 B.D.2号機 獲得済 COST: 260″​​​​​}, ​​​​​{​​​​​​​sortieTimes:0, ​​​​​​​msName:”格 プロトタイプガンダム 獲得済 COST: 280″​​​​​}, ​​​​​{​​​​​​​sortieTimes:0, ​​​​​​​msName:”近 パワード・ジム 獲得済 COST: 240″​​​​​}, ​​​​​{​​​​​​​sortieTimes:0, ​​​​​​​msName:”射 デザート・ジム 獲得済 COST: 160″​​​​​}, ​​​​​{​​​​​​​sortieTimes:0, ​​​​​​​msName:”遠 量産型ガンキャノン 獲得済 COST: 200″​​​​​}​​​}

set cRec to {msList:b1List, sortieDate:date string of (current date)}

–>  {​​​​​msList:{​​​​​​​{​​​​​​​​​sortieTimes:66, ​​​​​​​​​msName:”近 装甲強化型ジム 獲得済 COST: 200″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:43, ​​​​​​​​​msName:”遠 ジム・キャノン 獲得済 COST: 160″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:42, ​​​​​​​​​msName:”近 ザクII(F2) 獲得済 COST: 160″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:32, ​​​​​​​​​msName:”近 ジム・コマンド 獲得済 COST: 200″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:28, ​​​​​​​​​msName:”近 ジム(WD隊) 獲得済 COST: 160″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:24, ​​​​​​​​​msName:”近 陸戦型ガンダム 獲得済 COST: 220″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:22, ​​​​​​​​​msName:”近 ジム改 獲得済 COST: 240″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:22, ​​​​​​​​​msName:”遠 ガンタンク 獲得済 COST: 200″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:20, ​​​​​​​​​msName:”格 ジム(指揮官機) 獲得済 COST: 160″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:19, ​​​​​​​​​msName:”近 ジム 獲得済 COST: 120″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:14, ​​​​​​​​​msName:”遠 量産型ガンタンク 獲得済 COST: 160″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:12, ​​​​​​​​​msName:”格 陸戦型ジム 獲得済 COST: 120″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:11, ​​​​​​​​​msName:”格 ガンダム 獲得済 COST: 280″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:9, ​​​​​​​​​msName:”近 ジム・トレーナー 獲得済 COST: 120″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:9, ​​​​​​​​​msName:”射 ジム・スナイパーII(WD隊) 獲得済 COST: 220″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:7, ​​​​​​​​​msName:”射 陸戦型ガンダム(ジム頭) 獲得済 COST: 200″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:6, ​​​​​​​​​msName:”格 ガンダムEz8 獲得済 COST: 240″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:6, ​​​​​​​​​msName:”近 ジム・寒冷地仕様 獲得済 COST: 200″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:6, ​​​​​​​​​msName:”狙 ジム・スナイパーカスタム 獲得済 COST: 200″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:4, ​​​​​​​​​msName:”格 ジム・ストライカー 獲得済 COST: 180″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:3, ​​​​​​​​​msName:”格 ガンキャノン重装型 獲得済 COST: 160″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:2, ​​​​​​​​​msName:”近 アクア・ジム 獲得済 COST: 160″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:2, ​​​​​​​​​msName:”射 ガンキャノン 獲得済 COST: 200″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:1, ​​​​​​​​​msName:”近 ジム・コマンドライトアーマー 獲得済 COST: 160″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:0, ​​​​​​​​​msName:”格 ボールK型 獲得済 COST: 120″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:0, ​​​​​​​​​msName:”格 B.D.2号機 獲得済 COST: 260″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:0, ​​​​​​​​​msName:”格 プロトタイプガンダム 獲得済 COST: 280″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:0, ​​​​​​​​​msName:”近 パワード・ジム 獲得済 COST: 240″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:0, ​​​​​​​​​msName:”射 デザート・ジム 獲得済 COST: 160″​​​​​​​}, ​​​​​​​{​​​​​​​​​sortieTimes:0, ​​​​​​​​​msName:”遠 量産型ガンキャノン 獲得済 COST: 200″​​​​​​​}​​​​​}, ​​​​​sortieDate:”2015年7月10日金曜日”​​​}

set aName to “efsf.plist”
saveRecordToFolAsPlist(cRec, “戦場の絆”, aName) of me

on saveRecordToFolAsPlist(theRecord, folName, aName)
  
  
set myAppSupDir to (path to application support from user domain) as string
  
set myAppSupDir to myAppSupDir & folName & “:”
  
set aPath to (POSIX path of myAppSupDir)
  
try
    do shell script “mkdir -p” & space & (quoted form of aPath)
  end try
  
  
set theDict to current application’s NSDictionary’s dictionaryWithDictionary:theRecord
  
  
set thePath to aPath & aName –Full Path
  
set cPath to current application’s NSString’s stringWithString:thePath
  
–set cPath to cPath’s stringByExpandingTildeInPath()—(OSからフルパスを取得しているので)チルダなどの展開処理は省略
  
  
set aRes to theDict’s writeToFile:cPath atomically:true
  
  
return aRes as boolean
  
end saveRecordToFolAsPlist

★Click Here to Open This Script 

2015/05/29 BridgePlus v1.1.0 Readme日本語翻訳版

(Written By Shane Stanley、Translated By Takaaki Naganoya〜翻訳・掲載はShaneの許諾済み)

BridgePlusはAppleScriptで記述した「Script Library」で、バージョン10.9以降のOS Xで動作します。

典型的なScripterにとって、BridgePlusは「ASObjC Runner.app」の機能をもとにしたハンドラ群を含んだライブラリです。

AppleScriptObjCユーザーにとって、AppleScriptとCocoaオブジェクトの相互変換機能を向上させたコマンド群で、(AppleScript用語辞書によるラッピングを介さず)拡張機能に直接アクセスが可能なものです。

bridge.png

BridgePlusは、いずれかのScript Librariesフォルダ(~/Library/Script Libraries/ など)にインストールしておく必要があります。また、Scriptアプリケーションのバンドル内に直接埋め込むことも可能です(後述)。

基本的な利用方法

もし、あなたがAppleScriptObjCを使うつもりがない場合、BridgePlusは1つの適切なコマンドをAppleScript用語辞書の中に持っています。それが、「open lib description」コマンドです。本コマンドを実行すると、このReadme書類(英文)を自動でオープンします。

AppleScript名:sample1
use BridgePlus : script “BridgePlus”
open lib description

★Click Here to Open This Script 

BridgePlusライブラリ内のハンドラ(サブルーチン)を呼び出すためには、ライブラリ経由で呼び出すことになります。

たとえば、ソーティング(データ並べ替え)のハンドラ(サブルーチン)、

on sortListOfNumbers:listOfNumbers

を呼び出すためには、

AppleScript名:sample2
use AppleScript version “2.4″
use scripting additions
use BridgePlus : script “BridgePlus”
set sortedList to BridgePlus’s sortListOfNumbers:{9, 2, 7, 3, 6, 4}
–> {2, 3, 4, 6, 7, 9}

★Click Here to Open This Script 

のように書くことになります。もし、OS X 10.6.x上でのAppleScriptObjCハンドラ風に、

AppleScript名:sample3
use AppleScript version “2.4″
use scripting additions
use BridgePlus : script “BridgePlus”
set sortedList to BridgePlus’s sortListOfNumbers_({9, 2, 7, 3, 6, 4})

★Click Here to Open This Script 

と書いたとしても、コンパイル(構文確認)すると、

AppleScript名:sample4
use AppleScript version “2.4″
use scripting additions
use BridgePlus : script “BridgePlus”
set sortedList to BridgePlus’s sortListOfNumbers:{9, 2, 7, 3, 6, 4}

★Click Here to Open This Script 

のように書き換えられます。

(注記:オリジナルではここで各種ハンドラの実例紹介。サンプル部分をすべて日本語訳していると時間がかかりすぎるので、Shaneと協議の結果、まずは日本語訳も実例以外の部分を掲載ということで話がまとまった)

基本的な利用方法

BridgePlusは2つのメインコマンドを持っており、これらはAppleScriptObjC記述時に、CocoaとAppleScriptのオブジェクトの変換をシンプルにし、機能向上させるものです。

 Cocoaify:AppleScript側の値をCocoa側の等しい型の値(例:integer→NSInteger)に変換します
 ASify from:Cocoa側の値をAppleScript側の等しい型の値(例:NSInteger→integer)に変換します

BridgePlus Script LibraryのAppleScript用語辞書を見るには、

 スクリプトエディタ:BridgePlusを~/Library/Script Libraries.フォルダにコピーしたのちに、「ファイル」>「用語説明を開く…」を実行
 Script Debugger:Optionキーを押しながらScript Debuggerアイコンにドラッグ

と操作してください。

多くのケースにおいて、AppleScriptObjCによる処理は以下の3ステップから構成されます。

 AppleScript側の値をCocoa側の等しい型に変換
 Cocoaオブジェクトのinstance methodを呼び出す
 7覯漫Cocoaの値)をAppleScript側の値に変換して返す

たとえば、以下のようなAppleScriptObjCのコードがあったとして、

AppleScript名:sample5
use framework “Foundation”

set someList to {1, 3, 2} –I added :-)
set anArray to current application’s NSArray’s arrayWithArray:someList
set anArray to anArray’s sortedArrayUsingSelector:“compare:”
set aList to anArray as list

★Click Here to Open This Script 

これはBridgePlusを使ってこのように書き換えられます。

AppleScript名:sample6
use framework “Foundation”
use script “BridgePlus”

set someList to {1, 3, 2} I added :-)
set anArray to Cocoaify someList
set anArray to anArray’s sortedArrayUsingSelector:“compare:”
set aList to ASify from anArray

★Click Here to Open This Script 

ほんのちょっとシンプルになりましたが、劇的なほどではありませんね。しかしながら、「Cocoaify」「ASify from」コマンドを使うことによる利点は、AppleScriptとCocoaの間の型変換をより完全に行えることにあります。

・Cocoaifyコマンド:AppleScript側の値がdateオブジェクト、もしくはlist型やRecord型でdateオブジェクトを含む場合に、dateはNSDateに変換されます。alias型やfileはNSURLに変換されます

・ASify fromコマンド:Cocoa側の値がNSDateの場合、およびNSDateを含むNSArrayやNSDictionaryの場合、AppleScript側のdateに変換されます。同様に、NSURLはfile («class furl»)に変換されます。

Cocoa側の値がFloating Pointの数値を含む場合、それらはAppleScript側のrealに変換されますし、listやrecord内に含まれるrealとして変換されます。実際に、OS X 10.10.3上でCocoaのFloating Pointの数値1.2をAppleScript側のrealに変換すると1.200000047684になるなど、AppleScriptの標準機能を使うと精度が失われてしまいます。でも、BridgePlusの「ASify from」コマンドを使えば、こんな問題は起きません。Cocoaの1.2を変換しても、AppleScript側でも1.2が得られます。

なので、たとえば上記のサンプルScriptにAppleScriptのdate型のデータをlist型データに入れても、きちんとソートされます。

AppleScript名:sample7
use framework “Foundation”
use scripting additions
use script “BridgePlus”

set someList to {current date, (current date) - (2 * days), (current date) - (1 * days)} Today, The Day before Yesterday, Yesterday
set anArray to Cocoaify someList
set anArray to anArray’s sortedArrayUsingSelector:“compare:”
set aList to ASify from anArray
–> {date “2015年5月28日木曜日 0:22:39″, date “2015年5月29日金曜日 0:22:39″, date “2015年5月30日土曜日 0:22:39″}

★Click Here to Open This Script 

BridgePlusなしでAppleScriptの標準機能を使った場合には、サブルーチンを用いてひとつひとつのdateの値をNSDateに変換してから並べ替える必要があります。さらに、並べ替えたあとに元の型に変換し直す必要も出てきます。そのさいのAppleScriptObjCのコードはより長く複雑なものになってしまいます。

高度なAppleScriptObjCでの利用方法

「BridgePlus.scptd」のバンドルは、多くのScripterにとって有用なフレームワーク(BridgePlus.framework)を含んでいます。本ライブラリBridgePlusはこの内部フレームワークが持つすべての機能をサポートする用語は持っていませんが、あなたのScriptに「use script “BridgePlus”」の1行を加えるだけで、フレームワークのmethodに直接アクセスできるようになります。

フレームワーク内のmethodのほとんどは、「SMSForder」クラスから呼び出せるmethodとして実装されており、これらは「ASObjC Runner.app」(現在では廃止)をもとに機能拡張されたものです。「SMSForder」クラスのヘッダーファイルを確認するには、「open framework header」コマンドを実行します。

AppleScript名:sample8
use script “BridgePlus”
open framework header

★Click Here to Open This Script 

BridgePlusのフレームワークは、実際のところ「ASObjCExtras.framework」のアップデート版であり、ASObjCExtras.frameworkがすでにインストールされている環境でのコンフリクトやクラッシュを避けるために名称変更されたものです。

SMSForder内のmethodを直接呼び出す前に、最低でも1つのBridgePlus.scptdライブラリ内のコマンドを実行する必要があります。それが「load framework」コマンドです(もしくは何かBridgePlusの持つコマンド・・・たとえば、Cocoaify “” を実行することでもOKです)。

かんたんなサンプル:

AppleScript名:sample9
use framework “Foundation”
use scripting additions
use script “BridgePlus”

load framework
set newList to ASify from (current application’s SMSForder’s colsToRowsIn:{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}} |error|:(missing value))
>  {{1, 4, 7, 10}, {2, 5, 8, 11}, {3, 6, 9, 12}}

★Click Here to Open This Script 

やってはいけないことがあります。たとえあなたがBridgePlusライブラリをインストールしてあって使っていても、Script中に「use framework “BridgePlus”」とは書いてはいけません。その書き方ではBridgePlusバンドル内部のフレームワークは見つかりません。「use script “BridgePlus” 」の行とBridgePlusの持つコマンドを1つ実行するとフレームワークが(自動で)読み込まれるようになっています。

SMSForderはCocoa的に0からはじまるインデックスを用います、一方でBridgePlusライブラリの「AppleScriptObjC」で直接内部機能を呼び出さないハンドラを呼び出した場合にはAppleScriptの世界で標準的な1からはじまるインデックスで値を返します。

実行環境へのインストール(Deployment)

「~/Library/Script Libraries」フォルダに「BridgePlus.scptd」ファイルを入れることで、あなたのすべてのAppleScript中でBridgePlusの機能を利用できるようになります。

BridgePlusライブラリをあなたの書いたScriptと一緒に配布する際には、BridgePlus.scptdファイルをあなたのScriptのBundle内の「/Contents/Resources/Script Libraries」フォルダに入れておけば大丈夫です(アプレットの場合でも、.scptdファイルの場合でも)。

もし、あなたがScript Bundle(AppleScriptアプレット/.scptd)をCodesignすることを検討している場合、まず最初にライブラリ中に入っているBridgePlus.frameworkをcodesignする必要があります。

BridgePlusをXcodeプロジェクト内でお使いの場合、「BridgePlus.framework」をBridgePlusライブラリのバンドル内から取り出して、他のframeworkと同様にXcodeプロジェクトに登録し、「BridgePlus.scptd」バンドル内から削除する必要があります。このようにすれば、BridgePlus.frameworkのcodesignはXcodeがめんどうを見てくれます。

BridgePlusのダウンロードはこちら!
http://www.macosxautomation.com/applescript/apps/BridgePlus.html

著名なScripterでありBlogerであるTakaaki Naganoyaによる(表現原文ママ)ASObjCExtras.frameworkの総合マニュアル(英文)はこちらです。
同マニュアルは、もしあなたがBridgePlusの上級者向けの(AppleScriptObjCの)機能を利用したい場合には有用なものです。

http://piyocast.com/as/asinyaye/free-asobjcextras-scripting-guide/

本ライブラリの利用にあたって、ライブラリ全体、内部フレームワークのみの場合の両方で自由に再配布できます(再配布時の料金は発生しません)。
利用料金は発生しませんが、動作保証もいたしません。「At your own risk」でご利用ください。

2015/05/22 BridgePlusがv1.0からv1.1にバージョンアップ

Shane StanleyによるOSのScripting Bridgeのバグを回避しさまざまな高度な型変換機能や計算機能をAppleScriptにもたらす「ASObjCExtras.framework」の後継ソフトウェア、「BridgePlus」の登場から間もないですが、早くもバージョンアップ!

バージョン1.0から1.1になりました。

基本的な機能については、さほど変更はなく・・・AppleScriptObjCをさわっていないAppleScriptユーザーに便利で簡単に感じられるように、AppleScript用語辞書が補われているという方向で機能強化されています。FrameworkではAppleScript用語辞書がついていないので、そこを補うために「見た目のファイル形状」(Framework単体からFramework入りScript Librariesへ)を変更したということです。

BridgePlusのAppleScript用語辞書を用いて記述するかぎりは、結果などすべてAppleScriptのオブジェクトに変換して返してくれるので、いきなりNSArrayなどが返ってきて面食らうようなことはないはずです。Arrayのindexも0ベースではなくAppleScriptの1ベースのindexが返ってくるため、違和感をおぼえないようになっています。

ただ、これはつまり・・・値を返すときにかならず毎回CocoaオブジェクトからAppleScriptオブジェクトへの再変換を行っているわけで、高速なScriptを組むときにはこのオーバーヘッドを回避できるよう、内部機能を直接呼び出す方向でScriptを組んだほうがメリットがあります。

上級スクリプター向けの機能を維持しつつ、一般スクリプター向けにわかりやすさと単純さを高めた・・・という感じでしょうか。

AppleScript名:BridgePlus v1.0の記述サンプル
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use script “BridgePlus” version “1.0″

set someList to {1, 3, 2}
set anArray to Cocoaify someList
set anArray to anArray’s sortedArrayUsingSelector:“compare:”
set aList to ASify from anArray
–> {1, 2, 3}

★Click Here to Open This Script 

AppleScript名:BridgePlus v1.1の記述サンプル
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus” version “1.1″

set aList to BridgePlus’s sortListOfNumbers:{1, 3, 2}
–> {1, 2, 3}

★Click Here to Open This Script 

AppleScript名:BridgePlus v1.1の記述サンプル(上級者向け記述)
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus” version “1.1″

load framework –一応、念のため実行。初回呼び出し時のオーバーヘッド軽減のため
set someList to {1, 3, 2}
set anArray to Cocoaify someList
set anArray to anArray’s sortedArrayUsingSelector:“compare:”
set aList to ASify from anArray
–> {1, 2, 3}

★Click Here to Open This Script 

2015/05/21 ASObjC Extras.frameworkからBridgePlus Script Libraryへ

Scripting Bridgeのバグを回避し、より手軽にCocoaオブジェクトとAppleScriptオブジェクトの相互変換が行えるようにしたのが、Shane StanleyのASObjCExtras.frameworkです。

日々、便利に使っていますし・・・いくつかのアプリケーションやシステムはすでにASObjCExtras.frameworkを用いて記述し、稼働しています。

ただ、ここまで気合いの入ったASObjCExtras.frameworkですが、まだ大々的に利用されるにはいたっていません。

理由は・・・

(1)ASObjC自体が、Scripterにそれほど理解・利用されていない(Appleがドキュメントを一切出していないので、事実上Shaneの電子ブック「AppleScriptObjC Explored」と「Everyday AppleScriptObjC」を読まずに使うのは無理!)

(2)ASObjCExtras.frameworkにかぎらず、Cocoaの機能を使おうとしてもAppleScript用語辞書が存在していないので、「用語辞書を見ながらScriptを書く」というやり方ができない

といったあたりだとShaneは分析しており、加えて自分は、

(3)CocoaのオンラインドキュメントがObjective-CとかSwift用であって、AppleScriptObjCの書き方が書かれていない

さらに、

(4)Apple純正のScript Editorの機能不足

といった点も問題だと思っています。CocoaのAPIを調べて記述するだけの機能はScript Editorには備わっていません。

そこでShaneはASObjCExstras.frameworkの後継Framework「BridgePlus.framework」を含んだAppleScript Libraries「BridgePlus Library」を発表。配布を開始しました

AppleScript Librariesなので、AppleScript用語辞書が見られるし、useコマンドでバージョン番号も指定できる(割と頻繁にバージョンアップするので)など、さまざまな問題が解決できる見込みです。

bridgep.png

えー、一応事前に「こういうのやりたいんだけど(ドキュメントが作り直しになっちゃうかもしれないけど)、どうかな?」という打診はもらっていまして(^ー^;; そりゃー、Shaneがやりたいと思うんなら「一番いいと思う方向でやっちゃってくださいよ。ドキュメントのことは気にしないで」とお答えしておきました。

AppleScript名:ASObjCExtrasの記述サンプル
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aList to {1, 3, 2}
set aArray to current application’s SMSFord’s Cocoaify:aList
set aRes to aArray’s sortedArrayUsingSelector:“compare:”
set bList to aRes’s ASify() as list
–> {1, 2, 3}

★Click Here to Open This Script 

AppleScript名:BridgePlusの記述サンプル
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use script “BridgePlus” version “1.0″

set someList to {1, 3, 2}
set anArray to Cocoaify someList
set anArray to anArray’s sortedArrayUsingSelector:“compare:”
set aList to ASify from anArray
–> {1, 2, 3}

★Click Here to Open This Script 

2015/03/11 CSVファイルをListにParseする(ASOC)

Shane Stanley wrote me a simple but strong AppleScriptObjC (ASOC) Script. It is a perfect one!

I checked Objective-C version csv parser. But they are not so short as this.

And I understood that this method was made just for this purpose:-).

csv2.png
▲Test Data

AppleScript名:CSVのParse 4(ASOC)
– Created 2015-03-11 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set someString to read (choose file)

set theData to current application’s SMSFord’s arrayFromCSV:someString commaIs:“,”
set theData to (current application’s SMSFord’s subarraysIn:theData paddedWith:“” |error|:(missing value)) as list
–>  {​​​​​{​​​​​​​”cust1″, ​​​​​​​”prod,1″, ​​​​​​​”season 1″, ​​​​​​​”"​​​​​}, ​​​​​{​​​​​​​”cust1″, ​​​​​​​”prod1″, ​​​​​​​”season 2″, ​​​​​​​”"​​​​​}, ​​​​​{​​​​​​​”cust2″, ​​​​​​​”prod1″, ​​​​​​​”event1″, ​​​​​​​”season 1″​​​​​}, ​​​​​{​​​​​​​”cust2″, ​​​​​​​”prod2″, ​​​​​​​”event1″, ​​​​​​​”season 2″​​​​​}, ​​​​​{​​​​​​​”cust2″, ​​​​​​​”prod3″, ​​​​​​​”event1″, ​​​​​​​”season 1″​​​​​}​​​}

★Click Here to Open This Script 

2015/03/03 2点間の距離を求める

This AppleScript calculates the distance (km) between 2 places. Place data is a pair of Latitude and Longitude.

This Script requires ASObjCExtras Framework.

dist.png

2点間の距離を求めるAppleScriptです(単位:km)。位置情報は緯度と経度のペアで与えます。実行には、ASObjCExtras Frameworkのインストールが必要です。

AppleScript名:Calc Distance between 2 places v2
– Created 2015-03-03 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "ASObjCExtras"

set aPlace to {latitude:35.737072, longitude:139.637826} –Seibu-Ikebukuro Line Nakamurabashi Station (My Nearest Station)
set bPlace to {latitude:35.753108, longitude:139.595123} –Amusium OSC (Game Center, Nearest Senjo-No-Kizuna POD)

set aDistance to calcDistanceBetweenPlaces(aPlace, bPlace)
–>  4.251157707731 (km)

–2点間の距離を計測する(km単位)
–参考:http://log.nissuk.info/2012/04/2.html
on calcDistanceBetweenPlaces(aPlace, bPlace)
  
  
set r to 6378.137 –Equatorial radius/赤道半径(km)
  
  
–Place A
  
set lat1 to (latitude of aPlace) * pi / 180
  
set lng1 to (longitude of aPlace) * pi / 180
  
  
–Place B
  
set lat2 to (latitude of bPlace) * pi / 180
  
set lng2 to (longitude of bPlace) * pi / 180
  
  
–Calculation
  
set calc1 to (current application’s SMSFord’s sinValueOf:lat1) as real
  
set calc2 to (current application’s SMSFord’s sinValueOf:lat2) as real
  
  
set calc3 to (current application’s SMSFord’s cosValueOf:lat1) as real
  
set calc4 to (current application’s SMSFord’s cosValueOf:lat2) as real
  
  
set calc5 to (current application’s SMSFord’s cosValueOf:(lng2 - lng1)) as real
  
  
set aDist to (current application’s SMSFord’s acosValueOf:(calc1 * calc2 + calc3 * calc4 * calc5)) as real
  
set aDist to aDist * r
  
  
return aDist
  
end calcDistanceBetweenPlaces

★Click Here to Open This Script 

2015/02/24 ASObjCExtras Scripting Guide v1.0 Erratta

I uploaded ASObjC Extras.framework Scripting Guide v1.0 Erratta.

The description and sample of

subarraysFrom: usingKeys: outKeys: error:

was wrong in Scripting Guide v1.0. So, I rewrote it.

New v1.1 document is in progress. It contains “Embedding” chapter. My work stops due to the business of some works…So I post the errata at first.

erratta.png

2015/02/09 Photoshop Action Setをインストールする(ASOC)

PhotoshopのAction SetをインストールするAppleScriptのASOC版です。

AppleScript名:指定ファイルを指定Bundle IDのアプリでオープンする(ASOC)
– Created 2015-02-08 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”
use framework “AppKit”

set a to (choose file)
openFileWithApp(a, “com.adobe.photoshop”)

on openFileWithApp(aFileAlias as alias, aBundleID as string)
  set aURL to current application’s SMSFord’s URLFrom:aFileAlias
  
set aURLarray to current application’s NSArray’s arrayWithObject:aURL
  
set sharedWS to current application’s NSWorkspace’s sharedWorkspace()
  
  
sharedWS’s openURLs:aURLarray withAppBundleIdentifier:aBundleID options:(current application’s NSWorkspaceLaunchDefault) additionalEventParamDescriptor:(missing value) launchIdentifiers:(missing value)
end openFileWithApp

★Click Here to Open This Script 

2015/02/08 起動中のプロセスの存在確認

Bundle IDで指定したプロセスが起動しているかどうかを確認するAppleScriptです。

AppleScript的には、System Eventsを経由してプロセスの起動中確認を行います。

AppleScript名:指定Bundle IDのプロセス存在確認(AS)
use AppleScript version “2.4″
use scripting additions

set aRes to chkAppProcesByBundleID(“com.adobe.Photoshop”)
–> true

set aRes to chkAppProcesByBundleID(“com.adobe.Photoshopoo!”)
–> false

on chkAppProcesByBundleID(aBundleID as string)
  tell application “System Events”
    set a to every process whose bundle identifier is aBundleID
    
if length of a 1 then
      return true
    else
      return false
    end if
  end tell
end chkAppProcesByBundleID

★Click Here to Open This Script 

しかし、この方法ではもしもMac App Storeに申請するアプリケーションで、System Eventsへのアクセスが封じられたらおしまいです。一応、shell script経由でpsコマンドを実行するという手段もありますが・・・

そこで、ASOCでCocoaの機能を用いてプロセスの存在確認を行ってみました。もうちょっと短く書けそうな気配もするのですが、とりあえず。

AppleScript名:指定Bundle IDのプロセス存在確認(ASOC)
– Created 2015-02-08 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”
use framework “AppKit”

set aRes to chkAppProcesByBundleID(“com.adobe.Photoshop”)
–> true

set aRes to chkAppProcesByBundleID(“com.adobe.Photoshopoo!”)
–> false

on chkAppProcesByBundleID(aBundleID as string)
  set appArray to current application’s NSWorkspace’s sharedWorkspace’s runningApplications()
  
set appList to appArray’s ASify() as list
  
  
repeat with i in appList
    set anID to (i’s bundleIdentifier()) as string
    
if anID = aBundleID then
      return true
    end if
  end repeat
  
  
return false
end chkAppProcesByBundleID

★Click Here to Open This Script 

2015/01/24 ASOC on Xcodeでアプリケーションに埋め込めるようASObjCExtrasを改修中

ASObjCExtras.frameworkを使って、Xcode上でASOCによるプログラム開発は行えますが、それを配布できるようアプリケーション中に埋め込めるようになっているのか?

ASObjCExtrasは再配布に制限を設けられておらず、アプリケーションやアプレットに埋め込んで配布できるよう、それ自体にコードサインされていません。むしろ、そのように使えるように仕様が定められ、作られているはずのものです。

実際に行ってみたところ、ASObjCExtrasのバージョン1.2では「そうなっていなかった」という衝撃の事実が発覚(1日悩んでしまいました)。コンピュータの業界的にというべきか、プログラミング全般というべきか、マニュアルやドキュメントのとおりに実際のモノが作られているかは、自分の手で地道に確認する必要があります。これ、本当。

即座にShane Stanleyにフィードバックし、対策ビルドの開発が行われ・・・今朝方確認した未リリースの最新ビルドではきちんと埋め込めるようになっています(埋め込みおよび埋め込んだアプリケーションバンドル内のフレームワークを正しく参照するようになった)。

現在、Xcode上でASOCでアプリケーション開発を行っている方は、外部に配布なりMac App Storeでの配布・販売を考えている場合には、バージョン1.2からアップデートされたASObjCExtras.frameworkがリリースされるまで「待つ」ことをおすすめします最新バージョンのv1.2.3を埋め込むことを強く推奨します。

# とか書いている間に、新バージョンv1.2.3が公開されました。変更点はここに書いているように、Xcodeプロジェクトへの埋め込み対応改善です

以下、実際にXcode上のASOCのプロジェクトにASObjCExtras.frameworkを組み込んで、アプリケーションに同frameworkを埋め込む手順です。

fra1.png

fra2.png

fra3.png

fra4.png

fra5.png

fra6.png

あとは、ビルドしてアプリケーションバンドル内に/Contents/Frameworks/ASObjcExtrasが存在していることを確認し、ASObjCExtrasなどのサードパーティ製フレームワークが一切含まれていない別ユーザーアカウントにて動作するかどうか確認をすれば大丈夫でしょう。

2015/01/22 指定したアプリケーションの各lproj内の各stringsファイルのパス一覧を取得する

指定したアプリケーションの各言語のlprojフォルダ内にある、各stringsファイルへのパスを取得するAppleScriptです。

各アプリのローカライズ情報を取り出すプログラムはいろいろと試してはいるものの、作り方とかローカライズ方法が全然違うアプリケーションが紛れていることがあり、イレギュラーを除去すべきなのかも(Apple純正でもSafariあたりは全然違うし)。

AppleScript名:指定したアプリケーションの各lproj内の各stringsファイルのパス一覧を取得する
– Created 2015-01-22 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "ASObjCExtras"

set apPath1 to (path to applications folder) –dialog’s default directory
set anAlias to choose file of type "com.apple.application-bundle" default location apPath1 with prompt "ローカライズ情報を取得するアプリケーションを選択"
set aRes to getStringsPathWithinEachLanguageInApp(anAlias)
–> {{langStr:"English", stringsPath:{"/Applications/mi.app/Contents/Resources/English.lproj/Localizable.strings"}}, {langStr:"Japanese", stringsPath:{"/Applications/mi.app/Contents/Resources/Japanese.lproj/Localizable.strings"}}}

–指定したアプリケーションの各lproj内の各stringsファイルのパス一覧を取得する
on getStringsPathWithinEachLanguageInApp(anAlias)
  –アプリケーションのローカリゼーションを取得する(言語の重複を許容)
  
set locList to getSpecifiedAppFilesLocalizationListWithDuplication(anAlias) of me
  
  
set lprojList to {}
  
  
repeat with ii in locList
    
    
set jj to contents of ii
    
    
set appPath to POSIX path of anAlias
    
set appBundle to (current application’s NSBundle’s bundleWithPath:appPath)
    
set resPath to ((appBundle’s pathForResource:jj ofType:"lproj") as string) & "/"
    
    
if resPath is not equal to missing value then
      
      
set aLangBundle to (current application’s NSBundle’s bundleWithPath:resPath)
      
if aLangBundle is not equal to missing value then
        
        
set stringsList to (aLangBundle’s pathsForResourcesOfType:"strings" inDirectory:"") as list
        
        
if stringsList is not equal to {} then
          set the end of lprojList to {langStr:jj, stringsPath:stringsList}
        end if
        
      end if
    end if
    
  end repeat
  
  
return lprojList
  
end getStringsPathWithinEachLanguageInApp

–指定アプリケーションファイルの、指定Localeにおけるローカライズ言語リストを求める。重複を許容
on getSpecifiedAppFilesLocalizationListWithDuplication(anAppAlias as alias)
  
  
set aURL to (current application’s SMSFord’s URLFrom:anAppAlias)
  
set aBundle to current application’s NSBundle’s bundleWithURL:aURL
  
  
set theNSLocale to current application’s NSLocale’s localeWithLocaleIdentifier:"en" –Output Locale Name in English (en)
  
  
–Get Localization Info
  
set locList to aBundle’s localizations()
  
–> {"de", "English", "fr", "French", "German", "ja", "Japanese"}
  
  
return locList as list
  
end getSpecifiedAppFilesLocalizationListWithDuplication

★Click Here to Open This Script 

2015/01/20 Xcode上のASOCにScript Editor上のASOCの機能を移植する

最初、AppleScriptObjC(Cocoaの機能を呼び出せるAppleScript)というのは、Xcode上でしか書けないものでした(OS X 10.6で登場)。

asoc1.png

それが、10.7、10.8、10.9、10.10と進化するにしたがい、実行環境や記述環境が広がっていきました(これらのほかに、AutomatorやFileMaker Pro上の実行環境もありますが、ここでは扱いません)。

当初、OS X 10.6で導入されたXcode上のASOCは、同様にXcode上でGUIベースのAppleScriptアプリ開発を行う「AppleScript Studioの置き換え」と言われてきたため、Cocoaの機能を利用するのはもっぱらGUIまわりの部分だけで、内部処理の部分は従来のピュアなAppleScript(Cocoaの機能を利用しない)で作られることが多かったように思います(そうでないケースもありますが)。

OS X 10.10になってCocoaの機能を呼び出すASOCが、ほぼ通常のAppleScriptと不可分の位置付けとなったため、通常のAppleScriptでも積極的にCocoaの機能を利用するよう本Blogでも方向転換を行い、内部処理もCocoaの機能を呼び出すScriptでXcode上のASOCアプリケーション開発も行うようになりました。

実際に、実践的なAppleScriptのプログラム(レコードのリスト同士のdiffを求める)で試してみたところ・・・ピュアなAppleScriptだけで書いたものが420行、処理に10秒ちょっとかかっていたのが、ASOCで1からすべて書き直したところ行数が270行に(40%減)、処理速度は8.4倍の1.2秒までに短縮されました。

# 上記の処理時間計測は、考えられるMAXのサイズのテストデータを処理させたものです

asoc2.png

そこで、GUIアプリ開発環境であるXcode上のASOCにこのScriptを移し替えたいよね、という話に(必然的に)なるわけです。

ASOC on XcodeにASOC on Script EditorのScriptを

ASOC on Xcodeでは、プロジェクト作成時に自動的に作成されるdelegate scriptには、アプリケーション本体のさまざまな挙動(GUIに近い部分とか)を記述し、処理本体部分は別のScriptに追い出して記述するのが自分の流儀です。

というわけで、本体scriptから外部script(bundle内に存在)を呼び出すことになります。このあたりは、XcodeのInterface Builder上で、

asoc3.png

NSObjectの部品を画面上に配置して、そのNSObjectのclassを外部Scriptのscript文で指定したclassと同じものに設定。

asoc4.png

delegate script側で外部script連結用のpropertyを作成して、

asoc5.png

Interface Builder上でpropertyとNSObjectをつなげば、delegate script側から外部scriptのハンドラを呼び出せるようになります。まだ、ここまでは「準備」の部分です。

asoc62.png

ハンドラの書き方、呼び出し方が問題

〜delegate scriptから外部script上のハンドラを呼び出す

asoc7.png

外部script呼び出し用に作成したproperty(ここでは、diffLib)を用い、

  diffLib’s testMe:”TEST”

のように書きます。

呼び出される側のハンドラ側についてはOS X 10.10では、(1)パラメータの型指定、および(2)パラメータ省略時のデフォルト値指定ができるようになっていますが、ASOC on Xcodeでは(1)のみ可能でした。

  on testMe:aStr as string–parameter casting

(2)はコンパイル時にエラーになってしまいます。

  on testMe:aStr as string:”"–default value–Error
  on testMe(aParam as string:”")–Error

外部script内で他のハンドラを呼び出す

asoc8.png

ここで問題。Script Editor上のASOCで書いていたハンドラは、

  testSub(param1,param2)

のような書き方を(自分は)していたわけですが、これをすべてASOC流の、

  testSubWithp1: p2:

などと書き換えるのは大変です。

試行錯誤してみたところ、これを、

  my testSub(param1,param2)

と書けば、あまりASOC on Script Editorのハンドラ名称を書き換えなくても大丈夫でした(よかった)。myと書かないとコンパイルが通らないパターンとか、of meでもコンパイルが通るパターンもあるようで・・・安全のためにmyを使っています。

外部Scriptにおけるuse文

まだプロジェクトが終わっていないので、暫定情報ですが・・・

ASOC on Xcode上ではuse frameworkは書くとエラーになるので書きません。

一応、use AppleScript version “2.4″とかuse scripting additionsといった記述をscript節の内側に書いています。

asoc9.png

だいたいこんな感じでしょうか。試行錯誤をいろいろ行ったおかげで、思ったよりもうまく行っています。

2015/01/19 script節の中にASObjCExtrasの命令を記述する

ASObjCExtras.frameworkの機能を使って、いろいろと大きなScriptを書きはじめました。

そうすると、Script文で分割された巨大なAppleScriptなどもあるわけで・・・script節の中にASObjCExtrasの命令だったり、普通のCocoaの機能呼び出しの命令を書こうとして、実行時にエラーになったというところから話がはじまります。

プレーンなASOCのScriptだとこんな感じです。Script Editor上に記述して、実行できます。

AppleScript名:step1
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set a to retVerInfo()
–> “SMSFord 1.2.2″

on retVerInfo()
  set theVersionInfo to current application’s SMSFord’s |description|() as text
  
return theVersionInfo
end retVerInfo

★Click Here to Open This Script 

こうした書き方では何も問題はありません。

問題となるのはこんなふうに(step2)、script文(script clause)でくくった場合です。エラーになって実行できません。場合によっては、エディタごとクラッシュしてしまいます。

AppleScript名:step2
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set a to retVerInfo() of aLib
–> error “SMSFord は“description”メッセージを認識できません。” number -1708 from SMSFord

script aLib
  on retVerInfo()
    set theVersionInfo to current application’s SMSFord’s |description|() as text
    
return theVersionInfo
  end retVerInfo
end script

★Click Here to Open This Script 

いろいろと試行錯誤してみたところ、Xcode上のASOCのScriptと見比べてみて、parentの宣言が足りないということがわかりました。これに加えて、script節の冒頭にuse文を書いてframeworkの使用を宣言してみました。

AppleScript名:step3
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set a to retVerInfo() of aLib
–> “SMSFord 1.2.2″

script aLib
  
  
use framework “Foundation”
  
use framework “ASObjCExtras”
  
  
property parent : class “NSObject”
  
  
on retVerInfo()
    set theVersionInfo to current application’s SMSFord’s |description|() as text
    
display dialog theVersionInfo
    
    
return theVersionInfo
  end retVerInfo
  
end script

★Click Here to Open This Script 

Xcode上で記述するASOCのscriptにこうしたASObjCExtrasの利用を広げようとした場合、メインScript以外はこうした記述になるわけで、script節の中に記述する方法を明らかにしておく必要があったのでした。

2015/01/19 2Dリストから、指定アイテムNoで、指定データが該当する最初のものを返す v3

2D Listから、指定アイテムNoが指定データとイコールの要素を返すAppleScriptです。

{{”piyomaru”, 1}, {”Piyomaru Software”, 2}, {”Naganoya”, 3}, {”Takaaki”, 4}, {”MacBook Pro Retina mid 2012″, 5}}

という2D Listがあった場合に、各アイテム中のアイテムNo.が1(item 1)のデータが”Takaaki”のものについて、

{”Takaaki”, 4}

というデータを、該当するデータがなかった場合には{}を返します。

AppleScript名:2Dリストから、指定アイテムNoで、指定データが該当する最初のものを返す v3
– Created 2015-01-19 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "ASObjCExtras"

set aList to {{"piyomaru", 1}, {"Piyomaru Software", 2}, {"Naganoya", 3}, {"Takaaki", 4}, {"MacBook Pro Retina mid 2012", 5}}

set gList to searchInListByIndexItem(aList, 1, "Takaaki")
–> {"Takaaki", 4}

–2Dリストから、指定インデックスアイテムで、指定データが該当する最初のものを返す
on searchInListByIndexItem(aList as list, itemNum as integer, hitData as string)
  –ListからNSMutableSetへの型変換
  
set setKey to current application’s NSMutableSet’s setWithArray:aList
  
  
–Predicate Stringを組み立てる
  
if itemNum < 1 then return {}
  
set aPredicateStr to ("SELF[" & (itemNum - 1) as string) & "] == ’" & hitData & "’"
  
  
–抽出
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aPredicateStr
  
set aRes to (setKey’s filteredSetUsingPredicate:aPredicate)
  
set bRes to aRes’s allObjects()
  
  
–NSArrayからListに型変換して返す
  
set cRes to (bRes’s ASify()) as list
  
  
if cRes is not equal to {} then
    set cRes to contents of first item of cRes
  end if
  
  
return cRes
end searchInListByIndexItem

★Click Here to Open This Script 

2015/01/16 FM13をASから骨までしゃぶる

仕事で用件があってFileMaker Pro 13 AdvancedをOS X 10.10の環境にインストールしてみました。

一応最新のバージョンということになりますが、1年に1度バージョンアップを行ってきたFileMaker Proとしては、そろそろ13.5なり14なりのバージョンが出てもおかしくない頃です。

OS X 10.10のAppleScript環境に、FileMaker Pro 13はどの程度追いついているのでしょうか?(AppleScript用語辞書自体は、FM Pro v11から変わっていません)。

fm13_1a.png

Scriptステップ「AppleScriptを実行」にASOCのScriptを書いてみる

OS X 10.10で普通のAppleScriptにASOCのScriptをまぜて書けるようになったので、FileMaker ProのScriptステップ「AppleScriptを実行」にASOCを書いてみましょう。

fm13_2a.png

ASObjCExtras.frameworkのテスト用の機能を呼び出してみます。

fm13_3.png

予想外の結果が! 

fm13_4.png

ASOBjCExtras.frameworkは64bit onlyのframework。・・・ということは・・・FileMaker Pro 13というのは・・・

fm13_5.png

fm13_6.png

32bitアプリケーションだったんですね(汗) Shane Stanleyに一応フィードバックしておきましょう。

一応、ASObjCExtras.frameworkを使わないASOCのプログラムを実行させてみたら、「AppleScriptを実行」から実行できました。ただ、ASObjCExtrasが使えないと不便で仕方ないので、使えるようになってほしいところです。

# Xcode上でビルドオプションを変更すれば済むような話でもなさそうなので、実現するかどうかはちょっとわからないです、、、どうせFMも毎年アップデートするので、、、じきに64bitに、、、

JSXを書いて実行してみる!

これは、あっさり却下されました。記入欄にAppleScriptの文法にマッチしないものを書き込むとエラーになります。

ASOC経由でJavaScriptを実行してみる

ASOC経由でJavaScript Coreの機能を呼び出して、JavaScriptを実行させたところ実行できました。また、WebViewを動的に生成してJavaScriptの実行を行い、そちらもうまく実行できました。やはり、WebViewが使えると「できること」の範囲が広がります。

# 最初、WebViewの動的生成&JS実行は行えない、と書きましたが・・・追試を行い、問題なく実行できることを確認しました

異なるバージョンのFM間でScriptを相互呼び出し

異なるバージョンのFMを(2つ同時に起動しておいて)、「AppleScriptを実行」から異なるバージョンのFM DB上のScriptを実行できました。

2015/01/14 NSMutableIndexSetの初期化、作成

NSMutableIndexSetの初期化、データ作成のサンプルです。

NSMutableIndexSetの作成について、初期化の仕方がいまひとつ分かりませんでしたが、Shane Stanleyから「こうやればいいよ」と教えてもらったScript(そのまんま)です。

AppleScript名:NSMutableIndexSetの初期化、作成
–By Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set anIndexSet to current application’s NSMutableIndexSet’s indexSet() –initialize
anIndexSet’s addIndexesInRange:(current application’s NSMakeRange(3, 20)) –make range

set aList to (current application’s SMSFord’s arrayWithIndexSet:anIndexSet) as list
–>  {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}

★Click Here to Open This Script 

2015/01/14 ASObjCExtras.frameworkのScripting Guideを公開

本Blogでも多用しているShane Stanleyの「ASObjCExtras.framework」のScripting Guideを公開しました。フリーでご利用いただけます。すべて英文ですが、図や表を中心に構成しているため、そんなに難しくないと思います。

scripting_guide1.png

当初、Shane StanleyからASObjCExtras.frameworkのβ版の評価を依頼され、「Shane Stanleyのお願いだったら断るわけにはいかないでしょう」と、細々と始めたこの作業。ここまで行くとは思いませんでした(汗)。

ASObjCExtras.frameworkにはヘッダーファイルに詳細な説明があるんですが、あくまでObjective-Cを書ける人向けで、Scripterが見てもちょっと分かりづらいものでした。ASObjCExtras.frameworkについては、強力で桁違いの処理速度を実現できるので、とっつきづらいのは非常にもったいないと考えました。

また、すべての機能についてサンプルScriptが用意されているわけでもないので、実際に使ってみようとすると、かなりの試行錯誤が必要でした(過去形)。一部の機能については「これって必要なんだろうか?」と思うものもあります(用途がいまひとつイメージできない、とか)。

その逆に、「こういう命令は必要」というフィードバックを行い、追加された命令もいろいろあります。

評価・・・ということは「実際に使ってみる」ということで、実際にScriptを書いてみないと動きや機能が分かりません。必然的に、ASObjCExtras.frameworkのすべてのmethodを使ったサンプルScriptが手元にたまるわけで・・・なら、それを(モノによっては簡略化して)ドキュメントにまとめれば、Scripting ManualになるよねーということでShane Stanleyに提案。

img_2800.JPG

Scripting Guideで注意したのは、Cocoaの解説書を読んでいてつねづね思っていた「パラメータが具体的に何なのか分かりにくい」点への配慮でした。method名に書かれているパラメータがObjective-Cに不慣れな人間にはとても分かりづらく、しかもパラメータ数が増えると何がなんだかわからなくなるので、,箸△箸い辰慎号を用いて説明してみました。ここは、他の解説書にない改良点だと思います。

cocoa_method_reference.png

ドキュメントはすべてPagesで作成しましたが、このPagesにとても苦労させられました。とくにPagesの機能のなさ(リンクはPDFに書き出してAcrobat上で手で作成したし。ドキュメント内のリンク作成機能がないのは勘弁してほしいです)に苦しめられました。再利用のしやすさ(iBooks Autherに持って行きやすいとか)からPagesを選択しましたが、こんなに苦労するとは。

本Scripting Guideは、作成中のAppleScript本の体裁をほぼそのまま利用したので、そちらで作っている内容の検証とか、Pagesのノウハウ蓄積とか、そういう意味で今後の作業に(個人的に)フィードバックできるものと思われます。AppleScript本については、紙で出すとか出さないとかいう話もあるのですが、まだちょっと分かりません。

2015/01/07 ASObjCExtras ICU Transformのサンプル

ASObjCExtras.frameworkのScripting Manualを作成中で、すべての命令にサンプルコードを掲載しています。その一環としてテストしたサンプルScriptです。

scripting_manual.png

AppleScriptの世界ではICU(International Components for Unicode)は縁がない仕組みですが、ASObjCExtrasからその機能を呼び出せます(stringFrom: ICUTransform: inverse:)。

ICUのWebを見ると、いろいろと便利な変換機能が利用できることが見て取れますが、各言語間のアルファベット的な「変換」はしてくれても「翻訳」ではないので、その性質を知っておくことが重要と思われました。

半角-全角変換などはこのICUを使うと手軽に処理できてよいでしょう。

AppleScript名:ICUTransformのサンプル
–Sample Code
– Created 2015-01-06 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set theString to “ながのや, たかあき” –Hiragana
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Hiragana-Latin” inverse:false) as text
–> “naganoya, takaaki”

set theString to “ながのや, たかあき” –Hiragana
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Hiragana-Katakana” inverse:false) as text
–> “ナガノヤ, タカアキ”–Katakana

set theString to “Takaaki, Naganoya”
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Hiragana-Latin” inverse:true) as text
–> “たかあき、 ながのや”–Hiragana

set theString to “Takaaki, Naganoya”
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Katakana-Latin” inverse:true) as text
–> “タカアキ、 ナガノヤ”–Katakana

set theString to “Shane, Stanley”
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Katakana-Latin” inverse:true) as text
–> “シャネ、 スタンレイ”–Katakana…..this seems odd. “シェーン, スタンリー” will be a right spelling

set theString to “Takaaki, Naganoya”
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Fullwidth-Halfwidth” inverse:true) as text
–> “Takaaki, Naganoya”–Double Width Alphabet

set theString to “Naganoya, Takaaki”
set aRes to (current application’s SMSFord’s stringFrom:theString ICUTransform:“Latin-Hangul” inverse:false) as text
–> “나가노야, 타카아키“–Hangul

★Click Here to Open This Script 

2014/12/30 NSIndexSetに値を入れる、取り出す

ユニークな整数値のみをソートされた状態で保持するNSIndexSetを作成し、値を取り出すAppleScriptです。

NSIndexSetは値の重複を許さず、整数値のみ保持し、値をソートされた状態で保持するクラスで・・・AppleScriptから作成、値の追加、値の取り出しを行います。

正確にいえば、ここで扱っているのはNSIndexSetではなくNSMutableIndexSetです。

v2の方は、ASObjCExtras.frameworkの機能を利用して、一括でNSIndexSetから値を取り出しています。

AppleのReferenceを読むと、NSMutableIndexSetは10.9までしかinit()できないと書いてありますが・・・できているような、、、

AppleScript名:indexSetに値を入れる、取り出す
– Created 2014-12-30 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aindexSet to current application’s NSMutableIndexSet’s alloc()’s init()

repeat 100 times
  set aRandom to random number from 1 to 100
  
aindexSet’s addIndex:aRandom
end repeat

set aList to {}
set aInd to aindexSet’s firstIndex()

repeat
  if aInd = current application’s NSNotFound then exit repeat
  
set the end of aList to aInd as integer
  
set bInd to aindexSet’s indexGreaterThanIndex:aInd
  
copy bInd to aInd
end repeat

aList
–> {2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 28, 29, 30, 31, 32, 33, 34, 37, 38, 39, 40, 43, 52, 53, 56, 57, 59, 61, 63, 67, 70, 74, 76, 78, 79, 80, 81, 83, 84, 85, 87, 88, 89, 90, 91, 92, 94, 96, 97}

★Click Here to Open This Script 

AppleScript名:indexSetに値を入れる、取り出す v2
– Created 2014-12-30 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aindexSet to current application’s NSMutableIndexSet’s alloc()’s init()

repeat 100 times
  set aRandom to random number from 1 to 100
  
aindexSet’s addIndex:aRandom
end repeat

set aList to (current application’s SMSFord’s arrayWithIndexSet:aindexSet) as list

aList
–> {2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 28, 29, 30, 31, 32, 33, 34, 37, 38, 39, 40, 43, 52, 53, 56, 57, 59, 61, 63, 67, 70, 74, 76, 78, 79, 80, 81, 83, 84, 85, 87, 88, 89, 90, 91, 92, 94, 96, 97}

★Click Here to Open This Script 

AppleScript名:Listに値を入れる、取り出す(通常ASで書いた場合)
– Created 2014-12-31 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions

set aIndexSet to {}

repeat 100 times
  set aRandom to random number from 1 to 100
  
  
–ユニークな値のみをストアし、つねに昇順ソートされた状態を維持する
  
if aRandom is not in aIndexSet then –ユニーク判定
    set the end of aIndexSet to aRandom
    
set aIndexSet to shellSortAscending(aIndexSet) of me –昇順ソート
  end if
  
end repeat

aIndexSet
–> {1, 3, 4, 5, 6, 7, 8, 9, 11, 12, 14, 16, 17, 18, 19, 20, 21, 23, 26, 27, 28, 29, 30, 31, 34, 36, 37, 39, 40, 41, 43, 47, 49, 53, 55, 57, 58, 60, 63, 65, 66, 68, 71, 72, 73, 74, 76, 77, 80, 81, 82, 83, 84, 86, 87, 88, 91, 92, 95}

–1D Listの昇順ソート
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

★Click Here to Open This Script 

2014/12/29 文字エンコーディングを自動判別してファイル読み込み v1.1

Cocoaの機能を用いて、テキストファイルの文字エンコーディングを自動判別してテキストファイルを読み込むScriptの第3弾です。

最初に作ったv1と、Shane Stanleyから提案のあったYosemiteの新機能によるv2で、10KBぐらいの長めの日本語テキストをあらためて用意してテストしてみたところ、v2がUTF16BEとUTF16LEにまったく反応しないことが判明。

table2.png

ちょうどv1で仕方なく行っていたhexdumpの処理をShaneから提案のあった処理に置き換えてv1.1としたところ・・・v1系の方が反応できる文字エンコーディングが多いということに。

とりあえずこんな感じでしょうか?

AppleScript名:文字エンコーディングを自動判別してファイル読み込み v1.1
– Created 2014-12-28 by Takaaki Naganoya
– Changed 2014-12-29 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aPath to POSIX path of (choose file)
set aRes to readTextFile(aPath) of me

–Read Japanese text with detecting its text encoding
on readTextFile(aPath as string)
  
  
set aEnc to detectJapaneseEncodingAt(aPath) of me
  
if aEnc = false then return false
  
  
set aEncNum to aEnc as number
  
  
set encList to {“”, “”, current application’s NSJapaneseEUCStringEncoding, current application’s NSUTF8StringEncoding, “”, “”, “”, current application’s NSShiftJISStringEncoding, “”, current application’s NSUnicodeStringEncoding, current application’s NSWindowsCP1251StringEncoding, current application’s NSWindowsCP1252StringEncoding, current application’s NSWindowsCP1253StringEncoding, current application’s NSWindowsCP1254StringEncoding, current application’s NSWindowsCP1250StringEncoding, “”, “”, “”, “”, “”, current application’s NSISO2022JPStringEncoding}
  
  
if aEncNum > 21 then return false
  
  
set curEnc to contents of item aEncNum of encList
  
  
set {resValue, theError} to current application’s NSString’s stringWithContentsOfFile:aPath encoding:curEnc |error|:(reference)
  
return {aEncNum, resValue’s ASify() as string}
  
end readTextFile

–Auto detect text encoding
on detectJapaneseEncodingAt(aPOSIXpath as string)
  
  
–ISO2022JP check
  
set aNSData to current application’s NSData’s dataWithContentsOfFile:aPOSIXpath
  
set aDataLength to aNSData’s |length|()
  
if aDataLength > 1024 then set aDataLength to 1024
  
  
–0×1B check
  
set anNSString to current application’s NSString’s stringWithString:(character id 27) – 0×1B
  
set theData to anNSString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set theRange to aNSData’s rangeOfData:theData options:0 range:(current application’s NSMakeRange(0, aDataLength))
  
  
–found 0×1B in aNSData
  
if |length| of theRange = 1 and location of theRange < aDataLength then
    
    
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:21)
    
    
if aStr is not equal to missing value then
      return 21 – ISO2022JP
    end if
  end if
  
  
–Other Text Encodings
  
set encList to {“3″, “4″, “8″, “2415919360″, “2483028224″, “10″, “11″, “12″, “13″, “14″, “15″}
  
repeat with i in encList
    –UTF-16LE & BE parameters out of AppleScript number’s range. So, I held it in string data (But could not detect them….why?)
    
set j to contents of i
    
set aStr to (current application’s NSString’s stringWithString:j)
    
set aNum to aStr’s intValue()
    
    
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:aNum)
    
if aStr is not equal to missing value then
      return j as number
    end if
    
  end repeat
  
  
return false
  
end detectJapaneseEncodingAt

★Click Here to Open This Script 

2014/12/28 文字エンコーディングを自動判別してテキストファイル読み込み v1

指定のテキストファイルの文字エンコーディングを自動判別してテキストを読み込むAppleScriptです。

シフトJIS、UTF-8、EUC、ISO2022JP、UTF16、UTF16LE、UTF16(BOMなし)などで動作を確認しています(が、まだ実験段階です。それほどイジめていないので)。

UTF16LE、UTF16(BOMなし)については、エンコーディングの自動判定ルーチンで「UTF-16」として判定され、正直「使えないかな?」という気もしていたのですが、UTF-16として読み込ませてみたら、とくに問題なく(文字化けもなく)読み込めてしまったので、結果オーライです(?)。

本Scriptは対象のテキストファイルを複数回走査しています。SSDのマシンではまったく気になりませんが、HDD経由だとやや遅いと感じるかもしれません。

 1回目:とりあえず読み込んでデータサイズを取得
 2回目:hexdumpコマンドで先頭から1024バイトをダンプ
 3回目:ISO2022JPで読み込みテスト
 4回目:他のエンコーディングで読み込みテスト×複数回

ライブラリに放り込んで使えば、割と便利に使えることでしょう。最終的には、テキストファイルのオープンを行うためだけにテキストエディタを併用しなくても済むようにしたいところです(そんなに作り込む気はないんですけれど)。

AppleScript名:文字エンコーディングを自動判別してファイル読み込み
– Created 2014-12-28 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aPath to POSIX path of (choose file)
set aRes to readTextFile(aPath) of me

–指定パスのテキストファイルを、文字エンコーディングを判定しつつ読み込む
on readTextFile(aPath as string)
  
  
set aEnc to detectJapaneseEncodingAt(aPath) of me
  
if aEnc = false then return false
  
  
set aEncNum to aEnc as number
  
  
set encList to {“”, “”, current application’s NSJapaneseEUCStringEncoding, current application’s NSUTF8StringEncoding, “”, “”, “”, current application’s NSShiftJISStringEncoding, “”, current application’s NSUnicodeStringEncoding, current application’s NSWindowsCP1251StringEncoding, current application’s NSWindowsCP1252StringEncoding, current application’s NSWindowsCP1253StringEncoding, current application’s NSWindowsCP1254StringEncoding, current application’s NSWindowsCP1250StringEncoding, “”, “”, “”, “”, “”, current application’s NSISO2022JPStringEncoding}
  
  
if aEncNum > 21 then return false
  
  
set curEnc to contents of item aEncNum of encList
  
  
set {resValue, theError} to current application’s NSString’s stringWithContentsOfFile:aPath encoding:curEnc |error|:(reference)
  
return resValue as string
  
end readTextFile

–指定パスのテキストファイルの文字エンコーディングを自動判定する
on detectJapaneseEncodingAt(aPOSIXpath as string)
  
  
–ISO2022JPのチェック
  
set aNSData to current application’s NSData’s dataWithContentsOfFile:aPOSIXpath
  
set aDataLength to aNSData’s |length|()
  
if aDataLength > 0 then
    
    
if aDataLength > 1024 then
      set aDataLength to 1024
    end if
    
    
set aRes to do shell script “/usr/bin/hexdump -n “ & (aDataLength as string) & ” “ & quoted form of aPOSIXpath
    
set bList to (current application’s SMSFord’s arrayFromCSV:aRes commaIs:” “) as list
    
set cList to (current application’s SMSFord’s arrayByFlattening:bList) as list
    
set dList to (current application’s SMSFord’s indexesOfItems:{“1b”} inArray:cList inverting:false)
    
set dLen to dList’s |count|()
    
    
if dLen > 1 then –1以上でもよかったが、なんとなく
      –NSISO2022JPStringEncodingチェック
      
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:21)
      
if aStr is not equal to missing value then
        return 21 – ISO2022JP
      end if
    end if
  else
    return false
  end if
  
  
–他のテキストエンコーディングのチェック
  
set encList to {“3″, “4″, “8″, “10″, “11″, “12″, “13″, “14″, “15″, “2415919360″, “2483028224″}
  
repeat with i in encList
    –数字でパラメータを持っておくと、AppleScriptの数値の上限を超えて指数表示になるため、あえて文字列で保持
    
set j to contents of i
    
set aStr to (current application’s NSString’s stringWithString:j)
    
set aNum to aStr’s intValue()
    
    
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:aNum)
    
if aStr is not equal to missing value then
      return j
    end if
    
  end repeat
  
  
return false
  
end detectJapaneseEncodingAt

★Click Here to Open This Script 

2014/12/28 テキストから文字エンコーディングを推測する v3

(日本語の)テキストファイルから、文字エンコーディングを推測するAppleScriptの改良版です。

v1では判定できなかったEUCとISO2022JPの区別を行ってみました。とりあえず理解するために、ISO2022JPのファイルを作成してバイナリエディタ「0xED」でバイナリダンプ。

bindump.png

データの傾向を把握して、さまざまなサイトのObjective-Cのサンプルコードを検討。0×1bのコードの存在確認を行う必要があるんだな、ということはわかりました。

本当はv1とv3の間に失敗作のv2があり、NSData経由でテキストファイルをバイナリとしてアクセスしてみたのですが・・・やり方がよくなかったのか、実行するとエディタ(ASObjCExplorer 4)がコケまくります。さすがに、Cocoaオブジェクトのログが出力できるASObjCExplorer 4でも、誤った記述を行うとエディタごと落ちます。

というわけで、格好つけずにhexdumpして文字として存在確認という「いつもの手口」で処理してみたのが、このv3です。

結果は良好で、ISO2022JP、EUC、UTF8、SJIS、UTF16については検出できるようになりました。

UTF16-BEとUTF-16LEについては、なぜか見てはいけない気がして試していません。

AppleScript名:テキストから文字エンコーディングを推測する v3
– Created 2014-12-28 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

(*

property NSJapaneseEUCStringEncoding : 3 –EUC
property NSUTF8StringEncoding : 4 –UTF8
property NSShiftJISStringEncoding : 8 –SJIS
property NSUnicodeStringEncoding : 10 –UTF16

property NSWindowsCP1251StringEncoding : 11
property NSWindowsCP1252StringEncoding : 12
property NSWindowsCP1253StringEncoding : 13
property NSWindowsCP1254StringEncoding : 14
property NSWindowsCP1250StringEncoding : 15
property NSISO2022JPStringEncoding : 21
property NSUTF16BigEndianStringEncoding : 2415919360 —UTF16BE
property NSUTF16LittleEndianStringEncoding : 2483028224 —UTF16LE

*)

set a to POSIX path of (choose file)
set aRes to detectJapaneseEncodingAt(a) of me
return aRes

on detectJapaneseEncodingAt(aPOSIXpath as string)
  
  
set encList to {“3″, “4″, “8″, “10″, “11″, “12″, “13″, “14″, “15″, “2415919360″, “2483028224″}
  
set hitList to {}
  
  
set aNSData to current application’s NSData’s dataWithContentsOfFile:aPOSIXpath
  
  
  
–ISO2022JPのチェック
  
set aDataLength to aNSData’s |length|()
  
if aDataLength > 0 then
    
    
if aDataLength < 64 then
      set aMax to aDataLength
    else
      set aMax to 64
    end if
    
    
–先頭から64バイトだけチェックすればいいだろー(NSData経由でバイナリサーチを試してダメだったのでshell経由で)
    
set aRes to do shell script “/usr/bin/hexdump -n “ & (aMax as string) & ” “ & quoted form of aPOSIXpath
    
set bList to (current application’s SMSFord’s arrayFromCSV:aRes commaIs:” “) as list
    
set cList to (current application’s SMSFord’s arrayByFlattening:bList) as list
    
set dList to (current application’s SMSFord’s indexesOfItems:{“1b”} inArray:cList inverting:false) as list
    
set dLen to length of dList
    
    
if dLen > 2 then –1以上でもよかったが、なんとなく
      –NSISO2022JPStringEncodingチェック
      
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:21)
      
if aStr is not equal to missing value then
        return {21, aStr}
      end if
    end if
  else
    return false
  end if
  
  
  
–他のテキストエンコーディングのチェック
  
repeat with i in encList
    –数字でパラメータを持っておくと、AppleScriptの数値の上限を超えて指数表示になるため、あえて文字列で保持
    
set j to contents of i
    
set aStr to (current application’s NSString’s stringWithString:j)
    
set aNum to aStr’s intValue()
    
    
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:aNum)
    
if aStr is not equal to missing value then
      return {j, aStr}
    end if
    
  end repeat
  
  
return hitList
  
end detectJapaneseEncodingAt

★Click Here to Open This Script 

2014/12/27 PDFをページごとに分解する

指定したPDFファイルをページごとに分解して保存するAppleScriptです。

非常によくあるScript(PDFpenやSkimなどを使って分割)ですが、他のアプリを必要としません。

MacBook Pro Retina 2012(Core i7 2.66GHz)で277ページのPDFをページごとに分解するのに12秒でした。

AppleScript名:PDFをページごとに分解する
– Created 2014-12-26 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”
use framework “QuartzCore”

property PDFDocument : class “PDFDocument”

set aHFSPath to (choose file of type {“com.adobe.pdf”} with prompt “ページごとに分解するPDFを指定してください”) as string
set aURL to (current application’s SMSFord’s URLFrom:aHFSPath)

set aPOSIXpath to POSIX path of aHFSPath —書き出し先パスをPOSIX pathで用意しておく(あとで加工)

set aPDFdoc to PDFDocument’s alloc()’s initWithURL:aURL
set pCount to aPDFdoc’s pageCount()

–PDFをページごとに分割してファイル書き出し
repeat with i from 0 to (pCount - 1)
  
  
set thisPage to (aPDFdoc’s pageAtIndex:(i))
  
set thisDoc to (PDFDocument’s alloc()’s initWithData:(thisPage’s dataRepresentation()))
  
set outPath to addString_beforeExtensionIn_(“_” & (i + 1) as string, aPOSIXpath)
  
  (
thisDoc’s writeToFile:outPath) –書き出し
  
end repeat

–ファイルパス(POSIX path)に対して、文字列(枝番)を追加。拡張子はそのまま
on addString:extraString beforeExtensionIn:aPath
  set pathString to current application’s NSString’s stringWithString:aPath
  
set theExtension to pathString’s pathExtension()
  
set thePathNoExt to pathString’s stringByDeletingPathExtension()
  
  
set newPath to (thePathNoExt’s stringByAppendingString:extraString)’s stringByAppendingPathExtension:theExtension
  
return newPath as string
end addString:beforeExtensionIn:

★Click Here to Open This Script 

2014/12/26 record内のListにデータが入っているかどうか確認

record内のlistにデータが入っているか存在確認を行うAppleScriptです。

Cocoaの機能を用いて、入り組んだデータ構造にアクセスする練習の一環です。

まずは、list内の存在確認を行い……record内のlistへのアクセスを実施。

さらに、recordのlistへの一括チェックが行えるといいかも。

AppleScript名:List内に指定データが入っているかどうか確認
– Created 2014-12-26 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "ASObjCExtras"

set aList to {1, 2, 3, 4, 5}
set aArray to current application’s SMSFord’s Cocoaify:aList

set aRes to (aArray’s containsObject:2) as boolean
–> true

set bRes to (aArray’s containsObject:0) as boolean
–> false

★Click Here to Open This Script 

AppleScript名:record内のListにデータが入っているかどうか確認
– Created 2014-12-26 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "ASObjCExtras"

set aRec to {abc:100, bcd:{1, 2, 3, 4, 5}}
set aDic to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec

set aVal to ((aDic’s valueForKeyPath:"bcd")’s containsObject:2) as boolean
–> true

set bVal to ((aDic’s valueForKeyPath:"bcd")’s containsObject:100) as boolean
–> false

★Click Here to Open This Script 

2014/12/26 aListのうち、bListに入っていない項目を返す

1つのリスト(aList)の項目のうち、もう1つのリスト(bList)に入っていない項目を返すAppleScriptです。

プレーンなAppleScriptと、Cocoaの機能を利用したAppleScriptObjC(ASOC)で記述してみました。

プレーンなAppleScriptでは、「そもそもそういう機能はAppleScriptの処理系にはない」ので「全部自分で書く」ことになります。

ASOCでは、Cocoaが用意している(はずの)機能から探して呼び出すことになります。

プレーンなAppleScriptの場合は、処理を行うデータが膨大になればなるほど処理時間が長くなります。高速化する手段はいろいろありますが、高速化するとプログラムがものすごく長くなって読みにくくなります。

ASOCでは、データが増えてもプレーンなAppleScriptほどには処理時間は長くなりません。さらに高速化する手段はありませんが、最初から速いので問題はあまりありません。

xor_lists.png
▲ASとASOCの処理速度結果比較(10回実行した平均値)。10〜100倍以上ASOCが高速。ベンチマーク用データ作成部分を含む

いままで、プレーンなAppleScriptとASOCは記述環境が明確に分かれていました(AppleScriptエディタ、Xcode)が、OS X 10.10からはScript Editor上で普通に書いて使えるようになったので、場合に応じて使い分けるのがよいでしょう。

AppleScript名:aListのうち、bListに入っていない項目を返す
– Created 2014-12-26 by Takaaki Naganoya
– 2014 Piyomaru Software
set aList to {1, 2, 3, 4, 5, 6, 7, 8, 9}
set bList to {2, 5, 7}

set cList to exclusiveListUp(aList, bList)
–> {1, 3, 4, 6, 8, 9}

–aListのうち、bListに入っていない項目を返す
on exclusiveListUp(aList as list, bList as list)
  set newList to {}
  
repeat with i in aList
    set j to contents of i
    
if j is not in bList then
      set the end of newList to j
    end if
  end repeat
  
return newList
end exclusiveListUp

★Click Here to Open This Script 

AppleScript名:aListのうち、bListに入っていない項目を返す(ASOC)
– Created 2014-12-26 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aList to {1, 2, 3, 4, 5, 6, 7, 8, 9}
set bList to {2, 5, 7}

set cList to exclusiveListUp(aList, bList) of me
–> {1, 3, 4, 6, 8, 9}

–aListのうち、bListに入っていない項目を返す
on exclusiveListUp(aList as list, bList as list)
  set aArray to current application’s NSMutableArray’s arrayWithArray:aList
  
set bArray to current application’s SMSFord’s Cocoaify:bList
  
aArray’s removeObjectsInArray:bArray
  
return (aArray’s ASify() as list)
end exclusiveListUp

★Click Here to Open This Script 

2014/12/25 オーディオファイルのチャンネル数と再生時間を取得する

オーディオファイル(QuickTime Playerによるオーディオ収録ファイル、m4a)から情報を取得するAppleScriptです。

オーディオチャンネル数と、duration(再生時間:秒)を取得します。

AVAudioPlayerについては、playAtTime:がうまく動かなくて悩んでいます、、、

AppleScript名:オーディオファイルのチャンネル数と再生時間を取得する
– Created 2014-12-25 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”
use framework “AVFoundation”

set a to choose file of type {“com.apple.m4a-audio”}
set aURL to (current application’s SMSFord’s URLFrom:a)

set aAudioPlayer to current application’s AVAudioPlayer’s alloc()’s initWithContentsOfURL:aURL |error|:(missing value)
aAudioPlayer’s prepareToPlay()

set channelCount to aAudioPlayer’s numberOfChannels()
log {“channelCount”, channelCount}

set aDuration to aAudioPlayer’s duration()
–> 7355.9873015873 (Unit:Second)

★Click Here to Open This Script 

2014/12/25 アプリケーションごとのローカライズ言語数を求める

/Applicationsフォルダ以下の全アプリケーションのローカライズ言語数を求めて多い順にソートして返すAppleScriptです。

便利な部品ができてきたので、本来の用途以外に転用するのも簡単になってきました。

ただ、こういう「調査活動」以外にこの処理が生きるかどうかは未知数です、、、

OS X自体のローカライズ言語数=35ということが見て取れますが(ただし、対応言語は幾つかのレベルに分けられており、ユーザー数によって対応度が違うはず)、それ以上のローカライズ言語を持っているアプリケーションの存在が気になります。

AppleScript名:アプリケーションごとのローカライズ言語数を求める
– Created 2014-12-25 by Takaaki Naganoya

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

–/Applications以下のアプリケーションのパスをすべて求める
set apPath1 to (path to applications folder) as string
set aList to getFileListWithSpotLight(“kMDItemContentType”, “com.apple.application-bundle”, apPath1) of me

–各アプリケーションのローカリゼーション情報を取得(重複情報はカウントしつつ保持)
set aaList to {}

repeat with i in aList
  set j to contents of i
  
  
–アプリケーションのローカリゼーションを取得する(重複言語の除去ずみ)
  
set locList to getSpecifiedAppFilesLocalizationList(j) of me
  
  
tell application “System Events”
    set aName to name of j
  end tell
  
  
set the end of aaList to {aName, length of locList}
  
end repeat

–Sort Result (Many->Little)
set sortIndexes to {1} –Key Item id: begin from 0
set sortOrders to {false} –Descending Sort Order
set sortTypes to {“compare:”}
set rList to (current application’s SMSFord’s subarraysIn:aaList sortedByIndexes:sortIndexes ascending:sortOrders sortTypes:sortTypes |error|:(missing value)) as list

return rList
–> {{”VLC.app”, 93}, {”Creative Cloud.app”, 61}, {”Adobe Application Manager.app”, 61}, {”Google Drive.app”, 53}, {”Google Chrome.app”, 53}, {”Uninstall Product.app”, 43}, {”Uninstall Product.app”, 43}, {”Google Earth.app”, 42}, {”VueScan.app”, 40}, {”Evernote.app”, 40}, {”Adobe Muse CC 2014.app”, 37}, {”iTunes.app”, 35}, {”Digital Color Meter.app”, 35}, {”Image Capture.app”, 35}, {”Grab.app”, 35}, {”Game Center.app”, 35}, {”iBooks.app”, 35}, {”Boot Camp Assistant.app”, 35}, {”Bluetooth File Exchange.app”, 35}, {”System Information.app”, 35}, {”Safari.app”, 35}, {”Terminal.app”, 35}, {”Contacts.app”, 35}, {”Preview.app”, 35}, {”App Store.app”, 35}, {”Automator.app”, 35}, ……

–指定アプリケーションファイルの、指定Localeにおけるローカライズ言語リストを求める。重複を排除
on getSpecifiedAppFilesLocalizationList(anAppAlias as alias)
  
  
set aURL to (current application’s SMSFord’s URLFrom:anAppAlias)
  
set aBundle to current application’s NSBundle’s bundleWithURL:aURL
  
  
set theNSLocale to current application’s NSLocale’s localeWithLocaleIdentifier:“en” –Output Locale Name in English (en)
  
  
–Get Localization Info
  
set locList to aBundle’s localizations()
  
–> {”de”, “English”, “fr”, “French”, “German”, “ja”, “Japanese”}
  
set theEnumerator to locList’s objectEnumerator()
  
  
set theSet to current application’s NSMutableSet’s |set|()
  
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
    
set theName to (theNSLocale’s displayNameForKey:(current application’s NSLocaleIdentifier) value:aValue)
    
    
if theName is missing value then
      –”English”や”French”などは、missing valueが返ってくるので、”English”や”French”などをそのまま追加
      
theSet’s addObject:aValue
    else
      –”de”や”fr”などは、”German”や”French”が返ってくるので、それを追加
      
theSet’s addObject:theName
    end if
  end repeat
  
  
–NSCountedSetをNSMutableArrayに変換
  
set theArray to current application’s NSMutableArray’s array()
  
set theEnumerator to theSet’s objectEnumerator()
  
  
–NSMutableArrayから順次取り出して、NSArrayに出力
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
theArray’s addObject:aValue
  end repeat
  
  
return theArray as list
  
end getSpecifiedAppFilesLocalizationList

–指定階層下で、指定メタデータが指定パラメータであるファイルを取得(alias list)
on getFileListWithSpotLight(aMetaDataItem as string, aParam as string, startDir as {alias, string})
  set sDirText to quoted form of POSIX path of startDir
  
set shellText to “/usr/bin/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

★Click Here to Open This Script 

2014/12/25 アプリケーションのローカライズ分布を取得する v5

/Applicationsフォルダ内に入っているアプリケーションのローカライズ言語の度数分布を取得するAppleScriptのバージョンアップ版です。

1つのアプリケーションが複数の重複するローカライズ情報を持っているケース({”de”, “English”, “fr”, “French”, “German”, “ja”, “Japanese”})があり、1つのアプリケーションから情報を取得する段階で重複を除去するルーチンを作成。重複を排除して({”German”, “English”, “French”,”Japanese”})のように整理して取得するように変更しました。

また、/Applicationsフォルダの直下のアプリケーションのファイルしか取得していなかったので、mdfindで/Applicationsフォルダ以下すべてのアプリケーションを取得するように変更。

また、言語名ではなく出現頻度でソートするように変更しました。

途中で、mdfindでアプリケーションのファイルを抽出する処理を試していたら、サブフォルダ内のアプリケーションが出てこないという問題があり・・・開発マシンのSSD内のspotlightの検索辞書が壊れていたようで、再生成したら正常に動作するようになりました。mdfindの処理の落とし穴です。

マイナー言語(失礼!)をこまかくフォローしているのはGoogleアプリかと思っていたら、VLCでした。OSがサポートしていない言語用のローカライズを行っても意味はないのですが、ムービーの字幕表示などの関係でしょうか?

AppleScript名:アプリケーションのローカライズ分布を取得する v5
– Created 2014-12-23 by Takaaki Naganoya
– Changed 2014-12-24 by Shane Stanley
– Changed 2014-12-25 by Takaaki Naganoya

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

–mdfindで指定フォルダ以下にあるすべてのアプリケーションファイルを取得
set apPath1 to (path to applications folder) as string
set apList to getFileListWithSpotLight(“kMDItemContentType”, “com.apple.application-bundle”, apPath1) of me

set theCountedSet to current application’s NSCountedSet’s |set|()

–各アプリケーションのローカリゼーション情報を取得する
repeat with i in apList
  set j to contents of i
  
  
–指定アプリケーションのローカライズ言語を取得。重複を除去
  
set locList to getSpecifiedAppFilesLocalizationList(j) of me
  
–> {”de”, “English”, “fr”, “French”, “German”, “ja”, “Japanese”}
  
—-> {”German”, “English”, “French”,”Japanese”}
  
  
set locArray to (current application’s SMSFord’s Cocoaify:locList)
  
  
set theEnumerator to locArray’s objectEnumerator()
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
    (
theCountedSet’s addObject:aValue)
  end repeat
end repeat

–NSCountedSetをNSMutableArrayに変換
set theArray to current application’s NSMutableArray’s array()
set theEnumerator to theCountedSet’s objectEnumerator()

–NSMutableArrayから順次取り出して、NSArrayに対してDictionaryを追加
repeat
  set aValue to theEnumerator’s nextObject()
  
if aValue is missing value then exit repeat
  
theArray’s addObject:(current application’s NSDictionary’s dictionaryWithObjects:{aValue, (theCountedSet’s countForObject:aValue)} forKeys:{“theName”, “numberOfTimes”})
end repeat

–出現回数(numberOfTimes)で降順ソート
set theDesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“numberOfTimes” ascending:false
theArray’s sortUsingDescriptors:{theDesc}

return theArray as list

–> {{theName:”English”, numberOfTimes:733}, {theName:”Japanese”, numberOfTimes:404}, {theName:”French”, numberOfTimes:266}, {theName:”German”, numberOfTimes:265}, {theName:”Italian”, numberOfTimes:219}, {theName:”Spanish”, numberOfTimes:203}, {theName:”Dutch”, numberOfTimes:165}, {theName:”Chinese (China)”, numberOfTimes:149}, {theName:”Swedish”, numberOfTimes:133}, {theName:”Korean”, numberOfTimes:131}, {theName:”Russian”, numberOfTimes:129}, {theName:”Chinese (Taiwan)”, numberOfTimes:127}, {theName:”Portuguese”, numberOfTimes:124}, {theName:”Polish”, numberOfTimes:118}, {theName:”Danish”, numberOfTimes:117}, {theName:”Finnish”, numberOfTimes:110}, {theName:”Norwegian”, numberOfTimes:100}, {theName:”Czech”, numberOfTimes:92}, {theName:”Turkish”, numberOfTimes:90}, {theName:”Hungarian”, numberOfTimes:87}, {theName:”Greek”, numberOfTimes:76}, {theName:”Ukrainian”, numberOfTimes:75}, {theName:”Romanian”, numberOfTimes:74}, {theName:”Portuguese (Portugal)”, numberOfTimes:70}, {theName:”Slovak”, numberOfTimes:69}, {theName:”Catalan”, numberOfTimes:63}, {theName:”Croatian”, numberOfTimes:63}, {theName:”Arabic”, numberOfTimes:61}, {theName:”Thai”, numberOfTimes:61}, {theName:”Hebrew”, numberOfTimes:60}, {theName:”Indonesian”, numberOfTimes:58}, {theName:”Vietnamese”, numberOfTimes:57}, {theName:”Malay”, numberOfTimes:56}, {theName:”Spanish (Mexico)”, numberOfTimes:49}, {theName:”Base”, numberOfTimes:46}, {theName:”Portuguese (Brazil)”, numberOfTimes:22}, {theName:”Norwegian Bokmål”, numberOfTimes:21}, {theName:”Chinese (Simplified)”, numberOfTimes:19}, {theName:”Chinese”, numberOfTimes:14}, {theName:”English (United Kingdom)”, numberOfTimes:12}, {theName:”Bulgarian”, numberOfTimes:12}, {theName:”Chinese (Traditional)”, numberOfTimes:11}, {theName:”Latvian”, numberOfTimes:10}, {theName:”Lithuanian”, numberOfTimes:9}, {theName:”Slovenian”, numberOfTimes:9}, {theName:”Japanese (Japan)”, numberOfTimes:9}, {theName:”Czech (Czech Republic)”, numberOfTimes:9}, {theName:”Estonian”, numberOfTimes:8}, {theName:”Ukrainian (Ukraine)”, numberOfTimes:8}, {theName:”Spanish (Spain)”, numberOfTimes:7}, {theName:”Turkish (Turkey)”, numberOfTimes:7}, {theName:”Hungarian (Hungary)”, numberOfTimes:6}, {theName:”Danish (Denmark)”, numberOfTimes:6}, {theName:”Russian (Russia)”, numberOfTimes:6}, {theName:”French (XM)”, numberOfTimes:6}, {theName:”Swedish (Sweden)”, numberOfTimes:6}, {theName:”Serbian”, numberOfTimes:6}, {theName:”Norwegian Bokmål (Norway)”, numberOfTimes:6}, {theName:”Dutch (Netherlands)”, numberOfTimes:6}, {theName:”Persian”, numberOfTimes:5}, {theName:”Korean (South Korea)”, numberOfTimes:5}, {theName:”Finnish (Finland)”, numberOfTimes:5}, {theName:”Italian (Italy)”, numberOfTimes:5}, {theName:”German (Germany)”, numberOfTimes:5}, {theName:”French (Canada)”, numberOfTimes:5}, {theName:”French (France)”, numberOfTimes:5}, {theName:”Polish (Poland)”, numberOfTimes:5}, {theName:”Hindi”, numberOfTimes:5}, {theName:”English (United States)”, numberOfTimes:5}, {theName:”Hebrew (Israel)”, numberOfTimes:4}, {theName:”Arabic (United Arab Emirates)”, numberOfTimes:4}, {theName:”Spanish (Latin America)”, numberOfTimes:4}, {theName:”Romanian (Romania)”, numberOfTimes:4}, {theName:”Spanish (Namibia)”, numberOfTimes:4}, {theName:”Filipino”, numberOfTimes:4}, {theName:”Spanish (Laos)”, numberOfTimes:4}, {theName:”empty”, numberOfTimes:4}, {theName:”Greek (Greece)”, numberOfTimes:4}, {theName:”Telugu”, numberOfTimes:3}, {theName:”Malayalam”, numberOfTimes:3}, {theName:”Icelandic”, numberOfTimes:3}, {theName:”Kannada”, numberOfTimes:3}, {theName:”Galician”, numberOfTimes:3}, {theName:”Bengali”, numberOfTimes:3}, {theName:”Gujarati”, numberOfTimes:3}, {theName:”Marathi”, numberOfTimes:3}, {theName:”Tamil”, numberOfTimes:3}, {theName:”Belarusian”, numberOfTimes:3}, {theName:”Albanian”, numberOfTimes:2}, {theName:”Mongolian”, numberOfTimes:2}, {theName:”French (Morocco)”, numberOfTimes:2}, {theName:”Georgian”, numberOfTimes:2}, {theName:”Sinhala”, numberOfTimes:2}, {theName:”jp”, numberOfTimes:2}, {theName:”ua”, numberOfTimes:2}, {theName:”Amharic”, numberOfTimes:2}, {theName:”English (United Arab Emirates)”, numberOfTimes:2}, {theName:”Finnish (FL)”, numberOfTimes:2}, {theName:”Catalan (Spain)”, numberOfTimes:2}, {theName:”Basque”, numberOfTimes:2}, {theName:”English (Israel)”, numberOfTimes:2}, {theName:”Norwegian (NB)”, numberOfTimes:2}, {theName:”Oriya”, numberOfTimes:1}, {theName:”Punjabi”, numberOfTimes:1}, {theName:”Serbo-Croatian”, numberOfTimes:1}, {theName:”Walloon”, numberOfTimes:1}, {theName:”Corsican”, numberOfTimes:1}, {theName:”Sorani Kurdish”, numberOfTimes:1}, {theName:”Chinese (Hong Kong SAR China)”, numberOfTimes:1}, {theName:”Breton”, numberOfTimes:1}, {theName:”Cornish”, numberOfTimes:1}, {theName:”Chiga”, numberOfTimes:1}, {theName:”Aragonese”, numberOfTimes:1}, {theName:”Macedonian”, numberOfTimes:1}, {theName:”Japan”, numberOfTimes:1}, {theName:”Bosnian”, numberOfTimes:1}, {theName:”Ganda”, numberOfTimes:1}, {theName:”Tetum”, numberOfTimes:1}, {theName:”Welsh”, numberOfTimes:1}, {theName:”Armenian”, numberOfTimes:1}, {theName:”Kyrgyz”, numberOfTimes:1}, {theName:”Scottish Gaelic”, numberOfTimes:1}, {theName:”Afrikaans”, numberOfTimes:1}, {theName:”Zulu”, numberOfTimes:1}, {theName:”Swahili”, numberOfTimes:1}, {theName:”Burmese”, numberOfTimes:1}, {theName:”Chinese (Traditional, Taiwan)”, numberOfTimes:1}, {theName:”ct”, numberOfTimes:1}, {theName:”Tagalog”, numberOfTimes:1}, {theName:”Kazakh”, numberOfTimes:1}, {theName:”Bengali (India)”, numberOfTimes:1}, {theName:”Pashto”, numberOfTimes:1}, {theName:”Nepali”, numberOfTimes:1}, {theName:”Acoli”, numberOfTimes:1}, {theName:”Friulian”, numberOfTimes:1}, {theName:”Norwegian Nynorsk”, numberOfTimes:1}, {theName:”Asturian”, numberOfTimes:1}, {theName:”Thai (Thailand)”, numberOfTimes:1}, {theName:”Khmer”, numberOfTimes:1}, {theName:”Irish”, numberOfTimes:1}, {theName:”American”, numberOfTimes:1}, {theName:”Fulah”, numberOfTimes:1}, {theName:”Chinese (Traditional, Hong Kong SAR China)”, numberOfTimes:1}, {theName:”Occitan”, numberOfTimes:1}, {theName:”Interlingua”, numberOfTimes:1}, {theName:”Uzbek”, numberOfTimes:1}, {theName:”Azerbaijani”, numberOfTimes:1}}

–指定アプリケーションファイルの、指定Localeにおけるローカライズ言語リストを求める。重複を排除
on getSpecifiedAppFilesLocalizationList(anAppAlias as alias)
  
  
set aURL to (current application’s SMSFord’s URLFrom:anAppAlias)
  
set aBundle to current application’s NSBundle’s bundleWithURL:aURL
  
  
set theNSLocale to current application’s NSLocale’s localeWithLocaleIdentifier:“en” –Output Locale Name in English (en)
  
  
–Get Localization Info
  
set locList to aBundle’s localizations()
  
–> {”de”, “English”, “fr”, “French”, “German”, “ja”, “Japanese”}
  
set theEnumerator to locList’s objectEnumerator()
  
  
set theSet to current application’s NSMutableSet’s |set|()
  
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
    
set theName to (theNSLocale’s displayNameForKey:(current application’s NSLocaleIdentifier) value:aValue)
    
    
if theName is missing value then
      –”English”や”French”などは、missing valueが返ってくるので、”English”や”French”などをそのまま追加
      
theSet’s addObject:aValue
    else
      –”de”や”fr”などは、”German”や”French”が返ってくるので、それを追加
      
theSet’s addObject:theName
    end if
  end repeat
  
  
–NSCountedSetをNSMutableArrayに変換
  
set theArray to current application’s NSMutableArray’s array()
  
set theEnumerator to theSet’s objectEnumerator()
  
  
–NSMutableArrayから順次取り出して、NSArrayに対してDictionaryを追加
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
theArray’s addObject:aValue
  end repeat
  
  
return theArray as list
  
end getSpecifiedAppFilesLocalizationList

–指定階層下で、指定メタデータが指定パラメータであるファイルを取得(alias list)
on getFileListWithSpotLight(aMetaDataItem as string, aParam as string, startDir as {alias, string})
  set sDirText to quoted form of POSIX path of startDir
  
set shellText to “/usr/bin/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

★Click Here to Open This Script 

2014/12/24 アプリケーションのローカライズ分布を取得する v4

/Applicationsフォルダ内に入っているアプリケーションのローカライズ言語の度数分布を取得するAppleScriptのバージョンアップ版です。

Shane Stanleyからのクリスマスプレゼントともいえるもので、実に処理内容がクールです。いや、これはいい。

v3で言語表記の「ゆらぎ」をカバーしようとしましたが、このv4はOSのサービスを用いて、”jp”や”fr”といった短縮表記からフルネームの”Japanese”や”French”といった名称を展開し、そのうえで重複を排除しています。

AppleScript名:アプリケーションのローカライズ分布を取得する v4
– v1 Created 2014-12-24 By Takaaki Naganoya
– v2 Changed 2014-12-24 by Shane Stanley
– v3 Changed 2014-12-24 by Takaaki Naganoya
– v4 Changed 2014-12-24 by Shane Stanley

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set apPath to (path to applications folder) as string

tell application “Finder”
  tell folder apPath
    set aList to (every application file) as alias list
  end tell
end tell

set theCountedSet to current application’s NSCountedSet’s |set|()
set theNSLocale to current application’s NSLocale’s localeWithLocaleIdentifier:“en” –Output Locale Name in English (en)
–set theNSLocale to current application’s NSLocale’s currentLocale() –Output Locale Name in current Locale Language (ex. Japanese)

repeat with i in aList
  set j to contents of i
  
set aURL to (current application’s SMSFord’s URLFrom:j)
  
set aBundle to (current application’s NSBundle’s bundleWithURL:aURL)
  
  
–アプリケーションのローカリゼーションを取得する
  
set locList to aBundle’s localizations()
  
set theEnumerator to locList’s objectEnumerator()
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
set theName to (theNSLocale’s displayNameForKey:(current application’s NSLocaleIdentifier) value:aValue)
    
if theName is missing value then
      (theCountedSet’s addObject:aValue)
    else
      (theCountedSet’s addObject:theName)
    end if
  end repeat
  
  
log {“theCountedSet”, theCountedSet} –ASObjC Explorer 4だとCocoaのObjectも読める形式でログ出力可能
  
end repeat

–NSCountedSetをNSMutableDictionaryに変換してからASのrecordに変換
set theArray to current application’s NSMutableArray’s array()
set theEnumerator to theCountedSet’s objectEnumerator()
repeat
  set aValue to theEnumerator’s nextObject()
  
if aValue is missing value then exit repeat
  
theArray’s addObject:(current application’s NSDictionary’s dictionaryWithObjects:{aValue, (theCountedSet’s countForObject:aValue)} forKeys:{“theName”, “numberOfTimes”})
end repeat
set theDesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“theName” ascending:true
theArray’s sortUsingDescriptors:{theDesc}

return theArray as list
–> {{theName:”Acoli”, numberOfTimes:1}, {theName:”Afrikaans”, numberOfTimes:1}, {theName:”Albanian”, numberOfTimes:1}, {theName:”American”, numberOfTimes:1}, {theName:”Amharic”, numberOfTimes:2}, {theName:”Arabic”, numberOfTimes:36}, {theName:”Aragonese”, numberOfTimes:1}, {theName:”Armenian”, numberOfTimes:1}, {theName:”Asturian”, numberOfTimes:1}, {theName:”Azerbaijani”, numberOfTimes:1}, {theName:”Base”, numberOfTimes:30}, {theName:”Basque”, numberOfTimes:2}, {theName:”Belarusian”, numberOfTimes:2}, {theName:”Bengali”, numberOfTimes:3}, {theName:”Bengali (India)”, numberOfTimes:1}, {theName:”Bosnian”, numberOfTimes:1}, {theName:”Breton”, numberOfTimes:1}, {theName:”Bulgarian”, numberOfTimes:8}, {theName:”Burmese”, numberOfTimes:1}, {theName:”Catalan”, numberOfTimes:39}, {theName:”Chiga”, numberOfTimes:1}, {theName:”Chinese”, numberOfTimes:8}, {theName:”Chinese (China)”, numberOfTimes:69}, {theName:”Chinese (Hong Kong SAR China)”, numberOfTimes:1}, {theName:”Chinese (Simplified)”, numberOfTimes:14}, {theName:”Chinese (Taiwan)”, numberOfTimes:64}, {theName:”Chinese (Traditional)”, numberOfTimes:8}, {theName:”Chinese (Traditional, Hong Kong SAR China)”, numberOfTimes:1}, {theName:”Cornish”, numberOfTimes:1}, {theName:”Corsican”, numberOfTimes:1}, {theName:”Croatian”, numberOfTimes:40}, {theName:”Czech”, numberOfTimes:48}, {theName:”Danish”, numberOfTimes:62}, {theName:”Danish (Denmark)”, numberOfTimes:1}, {theName:”Dutch”, numberOfTimes:77}, {theName:”Dutch (Netherlands)”, numberOfTimes:1}, {theName:”English”, numberOfTimes:307}, {theName:”English (United Kingdom)”, numberOfTimes:7}, {theName:”English (United States)”, numberOfTimes:1}, {theName:”Estonian”, numberOfTimes:6}, {theName:”Filipino”, numberOfTimes:4}, {theName:”Finnish”, numberOfTimes:56}, {theName:”French”, numberOfTimes:130}, {theName:”Friulian”, numberOfTimes:1}, {theName:”Fulah”, numberOfTimes:1}, {theName:”Galician”, numberOfTimes:2}, {theName:”Ganda”, numberOfTimes:1}, {theName:”Georgian”, numberOfTimes:2}, {theName:”German”, numberOfTimes:135}, {theName:”Greek”, numberOfTimes:43}, {theName:”Gujarati”, numberOfTimes:3}, {theName:”Hebrew”, numberOfTimes:36}, {theName:”Hindi”, numberOfTimes:5}, {theName:”Hungarian”, numberOfTimes:44}, {theName:”Icelandic”, numberOfTimes:3}, {theName:”Indonesian”, numberOfTimes:38}, {theName:”Interlingua”, numberOfTimes:1}, {theName:”Irish”, numberOfTimes:1}, {theName:”Italian”, numberOfTimes:98}, {theName:”Japanese”, numberOfTimes:147}, {theName:”Japanese (Japan)”, numberOfTimes:1}, {theName:”Kannada”, numberOfTimes:3}, {theName:”Kazakh”, numberOfTimes:1}, {theName:”Khmer”, numberOfTimes:1}, {theName:”Korean”, numberOfTimes:67}, {theName:”Kyrgyz”, numberOfTimes:1}, {theName:”Latvian”, numberOfTimes:7}, {theName:”Lithuanian”, numberOfTimes:6}, {theName:”Macedonian”, numberOfTimes:1}, {theName:”Malay”, numberOfTimes:36}, {theName:”Malayalam”, numberOfTimes:3}, {theName:”Marathi”, numberOfTimes:3}, {theName:”Mongolian”, numberOfTimes:2}, {theName:”Nepali”, numberOfTimes:1}, {theName:”Norwegian”, numberOfTimes:49}, {theName:”Norwegian Bokmål”, numberOfTimes:10}, {theName:”Norwegian Bokmål (Norway)”, numberOfTimes:1}, {theName:”Norwegian Nynorsk”, numberOfTimes:1}, {theName:”Occitan”, numberOfTimes:1}, {theName:”Oriya”, numberOfTimes:1}, {theName:”Pashto”, numberOfTimes:1}, {theName:”Persian”, numberOfTimes:4}, {theName:”Polish”, numberOfTimes:60}, {theName:”Portuguese”, numberOfTimes:61}, {theName:”Portuguese (Brazil)”, numberOfTimes:12}, {theName:”Portuguese (Portugal)”, numberOfTimes:44}, {theName:”Punjabi”, numberOfTimes:1}, {theName:”Romanian”, numberOfTimes:42}, {theName:”Russian”, numberOfTimes:70}, {theName:”Russian (Russia)”, numberOfTimes:1}, {theName:”Scottish Gaelic”, numberOfTimes:1}, {theName:”Serbian”, numberOfTimes:5}, {theName:”Serbo-Croatian”, numberOfTimes:1}, {theName:”Sinhala”, numberOfTimes:2}, {theName:”Slovak”, numberOfTimes:42}, {theName:”Slovenian”, numberOfTimes:7}, {theName:”Sorani Kurdish”, numberOfTimes:1}, {theName:”Spanish”, numberOfTimes:95}, {theName:”Spanish (Latin America)”, numberOfTimes:2}, {theName:”Spanish (Mexico)”, numberOfTimes:28}, {theName:”Spanish (Spain)”, numberOfTimes:2}, {theName:”Swahili”, numberOfTimes:1}, {theName:”Swedish”, numberOfTimes:64}, {theName:”Swedish (Sweden)”, numberOfTimes:1}, {theName:”Tagalog”, numberOfTimes:1}, {theName:”Tamil”, numberOfTimes:3}, {theName:”Telugu”, numberOfTimes:3}, {theName:”Tetum”, numberOfTimes:1}, {theName:”Thai”, numberOfTimes:40}, {theName:”Thai (Thailand)”, numberOfTimes:1}, {theName:”Turkish”, numberOfTimes:46}, {theName:”Ukrainian”, numberOfTimes:39}, {theName:”Ukrainian (Ukraine)”, numberOfTimes:2}, {theName:”Uzbek”, numberOfTimes:1}, {theName:”Vietnamese”, numberOfTimes:37}, {theName:”Walloon”, numberOfTimes:1}, {theName:”Welsh”, numberOfTimes:1}, {theName:”Zulu”, numberOfTimes:1}, {theName:”empty”, numberOfTimes:3}}

–> {{theName:”American”, numberOfTimes:1}, {theName:”Base”, numberOfTimes:30}, {theName:”Chinese”, numberOfTimes:2}, {theName:”Czech”, numberOfTimes:1}, {theName:”Danish”, numberOfTimes:4}, {theName:”Dutch”, numberOfTimes:38}, {theName:”English”, numberOfTimes:212}, {theName:”Finnish”, numberOfTimes:1}, {theName:”French”, numberOfTimes:67}, {theName:”German”, numberOfTimes:72}, {theName:”Greek”, numberOfTimes:1}, {theName:”Hungarian”, numberOfTimes:1}, {theName:”Italian”, numberOfTimes:50}, {theName:”Japanese”, numberOfTimes:80}, {theName:”Korean”, numberOfTimes:3}, {theName:”Polish”, numberOfTimes:3}, {theName:”Portuguese”, numberOfTimes:2}, {theName:”Russian”, numberOfTimes:3}, {theName:”Spanish”, numberOfTimes:45}, {theName:”Swedish”, numberOfTimes:5}, {theName:”empty”, numberOfTimes:3}, {theName:”アイスランド語”, numberOfTimes:3}, {theName:”アイルランド語”, numberOfTimes:1}, {theName:”アストゥリアス語”, numberOfTimes:1}, {theName:”アゼルバイジャン語”, numberOfTimes:1}, {theName:”アチョリ語”, numberOfTimes:1}, {theName:”アフリカーンス語”, numberOfTimes:1}, {theName:”アムハラ語”, numberOfTimes:2}, {theName:”アラゴン語”, numberOfTimes:1}, {theName:”アラビア語”, numberOfTimes:36}, {theName:”アルバニア語”, numberOfTimes:1}, {theName:”アルメニア語”, numberOfTimes:1}, {theName:”イタリア語”, numberOfTimes:48}, {theName:”インターリングア”, numberOfTimes:1}, {theName:”インドネシア語”, numberOfTimes:38}, {theName:”ウェールズ語”, numberOfTimes:1}, {theName:”ウクライナ語”, numberOfTimes:39}, {theName:”ウクライナ語 (ウクライナ)”, numberOfTimes:2}, {theName:”ウズベク語”, numberOfTimes:1}, {theName:”エストニア語”, numberOfTimes:6}, {theName:”オック語”, numberOfTimes:1}, {theName:”オランダ語”, numberOfTimes:39}, {theName:”オランダ語 (オランダ)”, numberOfTimes:1}, {theName:”オリヤー語”, numberOfTimes:1}, {theName:”ガリシア語”, numberOfTimes:2}, {theName:”ガンダ語”, numberOfTimes:1}, {theName:”カザフ語”, numberOfTimes:1}, {theName:”カタロニア語”, numberOfTimes:39}, {theName:”カンナダ語”, numberOfTimes:3}, {theName:”ギリシャ語”, numberOfTimes:42}, {theName:”キルギス語”, numberOfTimes:1}, {theName:”グジャラート語”, numberOfTimes:3}, {theName:”グルジア語”, numberOfTimes:2}, {theName:”クメール語”, numberOfTimes:1}, {theName:”クルド語(ソラニー)”, numberOfTimes:1}, {theName:”クロアチア語”, numberOfTimes:40}, {theName:”コルシカ語”, numberOfTimes:1}, {theName:”コーンウォール語”, numberOfTimes:1}, {theName:”シンハラ語”, numberOfTimes:2}, {theName:”ズールー語”, numberOfTimes:1}, {theName:”スウェーデン語”, numberOfTimes:59}, {theName:”スウェーデン語 (スウェーデン)”, numberOfTimes:1}, {theName:”スコットランド・ゲール語”, numberOfTimes:1}, {theName:”スペイン語”, numberOfTimes:50}, {theName:”スペイン語 (スペイン)”, numberOfTimes:2}, {theName:”スペイン語 (メキシコ)”, numberOfTimes:28}, {theName:”スペイン語 (ラテンアメリカ)”, numberOfTimes:2}, {theName:”スロバキア語”, numberOfTimes:42}, {theName:”スロベニア語”, numberOfTimes:7}, {theName:”スワヒリ語”, numberOfTimes:1}, {theName:”セルビア語”, numberOfTimes:5}, {theName:”セルボ・クロアチア語”, numberOfTimes:1}, {theName:”タイ語”, numberOfTimes:40}, {theName:”タイ語 (タイ)”, numberOfTimes:1}, {theName:”タガログ語”, numberOfTimes:1}, {theName:”タミル語”, numberOfTimes:3}, {theName:”チェコ語”, numberOfTimes:47}, {theName:”チガ語”, numberOfTimes:1}, {theName:”デンマーク語”, numberOfTimes:58}, {theName:”デンマーク語 (デンマーク)”, numberOfTimes:1}, {theName:”テトゥン語”, numberOfTimes:1}, {theName:”テルグ語”, numberOfTimes:3}, {theName:”ドイツ語”, numberOfTimes:63}, {theName:”トルコ語”, numberOfTimes:46}, {theName:”ネパール語”, numberOfTimes:1}, {theName:”ノルウェー語”, numberOfTimes:49}, {theName:”ノルウェー語(ニーノシュク)”, numberOfTimes:1}, {theName:”ノルウェー語(ブークモール)”, numberOfTimes:10}, {theName:”ノルウェー語(ブークモール) (ノルウェー)”, numberOfTimes:1}, {theName:”バスク語”, numberOfTimes:2}, {theName:”パシュトゥー語”, numberOfTimes:1}, {theName:”パンジャブ語”, numberOfTimes:1}, {theName:”ハンガリー語”, numberOfTimes:43}, {theName:”ビルマ語”, numberOfTimes:1}, {theName:”ヒンディー語”, numberOfTimes:5}, {theName:”ブルガリア語”, numberOfTimes:8}, {theName:”ブルトン語”, numberOfTimes:1}, {theName:”フィリピノ語”, numberOfTimes:4}, {theName:”フィンランド語”, numberOfTimes:55}, {theName:”フラニ語”, numberOfTimes:1}, {theName:”フランス語”, numberOfTimes:63}, {theName:”フリウリ語”, numberOfTimes:1}, {theName:”ベトナム語”, numberOfTimes:37}, {theName:”ベラルーシ語”, numberOfTimes:2}, {theName:”ベンガル語”, numberOfTimes:3}, {theName:”ベンガル語 (インド)”, numberOfTimes:1}, {theName:”ヘブライ語”, numberOfTimes:36}, {theName:”ペルシア語”, numberOfTimes:4}, {theName:”ボスニア語”, numberOfTimes:1}, {theName:”ポルトガル語”, numberOfTimes:59}, {theName:”ポルトガル語 (ブラジル)”, numberOfTimes:12}, {theName:”ポルトガル語 (ポルトガル)”, numberOfTimes:44}, {theName:”ポーランド語”, numberOfTimes:57}, {theName:”マケドニア語”, numberOfTimes:1}, {theName:”マラヤーラム語”, numberOfTimes:3}, {theName:”マラーティー語”, numberOfTimes:3}, {theName:”マレー語”, numberOfTimes:36}, {theName:”モンゴル語”, numberOfTimes:2}, {theName:”ラトビア語”, numberOfTimes:7}, {theName:”リトアニア語”, numberOfTimes:6}, {theName:”ルーマニア語”, numberOfTimes:42}, {theName:”ロシア語”, numberOfTimes:67}, {theName:”ロシア語 (ロシア)”, numberOfTimes:1}, {theName:”ワロン語”, numberOfTimes:1}, {theName:”中国語”, numberOfTimes:6}, {theName:”中国語 (中国)”, numberOfTimes:69}, {theName:”中国語 (中華人民共和国香港特別行政区)”, numberOfTimes:1}, {theName:”中国語 (台湾)”, numberOfTimes:64}, {theName:”中国語 (簡体字)”, numberOfTimes:14}, {theName:”中国語 (繁体字)”, numberOfTimes:8}, {theName:”中国語 (繁体字、中華人民共和国香港特別行政区)”, numberOfTimes:1}, {theName:”日本語”, numberOfTimes:67}, {theName:”日本語 (日本)”, numberOfTimes:1}, {theName:”英語”, numberOfTimes:95}, {theName:”英語 (アメリカ合衆国)”, numberOfTimes:1}, {theName:”英語 (イギリス)”, numberOfTimes:7}, {theName:”韓国語”, numberOfTimes:64}}

★Click Here to Open This Script 

動作原理を理解するために、指定アプリ1つのローカライズ言語を取得するScriptを単体で動作するようにしてみました。

AppleScript名:選択したアプリケーションのローカライズ言語名称を求める
– Created 2014-12-23 by Takaaki Naganoya
– Changed 2014-12-24 by Shane Stanley
– Created 2014-12-24 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set a to choose file of type {“com.apple.application-bundle”}
set aRes to getSpecifiedAppFilesLocalizationList(a) of me
–> {”German”, “English”, “French”, “Japanese”}

–指定アプリケーションファイルの、指定Localeにおけるローカライズ言語リストを求める
on getSpecifiedAppFilesLocalizationList(anApp)
  
  
set aURL to (current application’s SMSFord’s URLFrom:anApp)
  
set aBundle to current application’s NSBundle’s bundleWithURL:aURL
  
  
set theNSLocale to current application’s NSLocale’s localeWithLocaleIdentifier:“en” –Output Locale Name in English (en)
  
  
–Get Localization Info
  
set locList to aBundle’s localizations()
  
–> {”de”, “English”, “fr”, “French”, “German”, “ja”, “Japanese”}
  
  
set outList to {}
  
  
repeat with i in locList
    set j to contents of i
    
set theName to (theNSLocale’s displayNameForKey:(current application’s NSLocaleIdentifier) value:j)
    
    
if theName is missing value then
      –”English”や”French”などは、missing valueが返ってくるので、”English”や”French”などをそのまま追加
      
if (j as string) is not in outList then
        set the end of outList to j as string
      end if
    else
      –”de”や”fr”などは、”German”や”French”が返ってくるので、それを追加
      
if theName as string is not in outList then
        set the end of outList to theName as string
      end if
    end if
  end repeat
  
  
return outList
  
end getSpecifiedAppFilesLocalizationList

★Click Here to Open This Script 

2014/12/24 アプリケーションのローカライズ分布を取得する v3

/Applicationsフォルダ内に入っているアプリケーションのローカライズ言語の度数分布を取得するAppleScriptのバージョンアップ版です。

言語の識別子がアプリケーションごとにゆらいでおり(Japanese/ja/ja_JPなど)、ゆらぎを吸収する処理を付加してみました。

通常のAppleScriptであれば、2D Listをループで回して、is in演算子で複数の値にヒットするかどうか処理を行うところですが、Cocoaの機能とASObjCExtras.frameworkの機能を活用して処理しました。おかげで、開発環境(MacBook Pro Retina 2012)では1秒以下で処理が終了します。

これは、処理速度もさることながら・・・AppleScriptのrecordに変換するとハイフン(「ー」)を含むようなラベルは処理がまったくできなくなってしまう(エラーになる)ため、それを回避する意味もあります。

AppleScript名:アプリケーションのローカライズ分布を取得する v3
– Created 2014-12-23 by Takaaki Naganoya
– Changed 2014-12-24 by Shane Stanley
– Changed 2014-12-24 by Takaaki Naganoya

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "ASObjCExtras"

–/Applications以下のアプリケーションのパスをすべて求める
set apPath to (path to applications folder) as string

tell application "Finder"
  tell folder apPath
    set aList to (every application file) as alias list
  end tell
end tell

–各アプリケーションのローカリゼーション情報を取得(重複情報はカウントしつつ保持)
set aDict to current application’s NSMutableDictionary’s alloc()’s init()
set theCountedSet to current application’s NSCountedSet’s |set|()

repeat with i in aList
  set j to contents of i
  
set aURL to (current application’s SMSFord’s URLFrom:j)
  
set aBundle to (current application’s NSBundle’s bundleWithURL:aURL)
  
  
–アプリケーションのローカリゼーションを取得する
  
set locList to aBundle’s localizations()’s ASify() as list
  (
theCountedSet’s addObjectsFromArray:locList)
  
  
–log {"theCountedSet", theCountedSet} –ASObjC Explorer 4だとCocoaのObjectも読める形式でログ出力可能
end repeat

–NSCountedSetをNSMutableDictionaryに変換
set theEnumerator to theCountedSet’s objectEnumerator()
repeat
  set aValue to theEnumerator’s nextObject()
  
if aValue is missing value then exit repeat
  
aDict’s setObject:(theCountedSet’s countForObject:aValue) forKey:aValue
end repeat

–言語区分リスト(実際には同一視してまとめる)。ものすごくざっくり。Googleのアプリだけものすごく狭い言語へのローカライズを行っているもよう
set langList to {{"Japanese", "ja", "ja_JP"}, {"English", "en", "Base", "American"}, {"Arabic", "ar"}, {"Arabic", "ar"}, {"Catalan", "ca"}, {"Czech", "cs"}, {"Danish", "da"}, {"Dutch", "nl"}, {"Greek", "el"}, {"Spanish", "es"}, {"Spanish@Latin America", "es_419"}, {"Finnish", "fi"}, {"French", "fr"}, {"German", "de"}, {"Hebrew", "he"}, {"Croatian", "hr"}, {"Hungarian", "hu"}, {"Italian", "it"}, {"Korean", "ko"}, {"Norwegian", "no"}, {"Polish", "pl"}, {"Portuguese@Brazil", "pt", "pt-BR"}, {"Portuguese@Portugal", "pt_PT"}, {"Romanian", "ro"}, {"Russian", "ru"}, {"Slovak", "sk"}, {"Swedish", "sv"}, {"Thai", "th"}, {"Setswana", "tr"}, {"Ukrainian", "uk"}, {"Chinese@China", "Chinese", "zh_CN", "zh", "zh-Hans"}, {"Chinese@HongKong", "zh_HK"}, {"Chinese@Taiwan", "zh_TW"}, {"Gujarati@India", "Gujarati", "gu"}, {"Bulgarian", "bg"}, {"Amharic@Ethiopia", "am"}, {"Asturian", "ast"}, {"Kazakh@Kazakhstan", "kk"}, {"Zulu, Bantu language spoken in South Africa", "zu"}, {"Filipino", "fil"}, {"Luganda", "lg"}, {"Danish@Denmark", "da_DK"}}
–すべてではないので、ちょっといい加減

–NSMutableDictionaryをArrayに入れて、複数ラベルの同一視処理を行いローカライズ言語ごとの実数の集計を行う
set bList to current application’s NSArray’s arrayWithObject:aDict
set cList to {}

repeat with i in langList
  set j to contents of i
  
  
set aArray to (current application’s SMSFord’s subarraysFrom:bList usingKeys:j outKeys:j |error|:(missing value)) –指定ラベルの項目の数値のみ抽出
  
set bArray to aArray’s lastObject() –2D Arrayで返ってくるので、1Dに
  
set cArray to (current application’s SMSFord’s arrayByReplacingNullsIn:bArray withItem:0) –Hitしなかった値がmissing valueで返るので、0に置換
  
  
–ヒットした数を合計
  
set sumNum to (cArray’s valueForKeyPath:"@sum.self")’s integerValue() as number
  
set the end of cList to {contents of first item of j, sumNum}
  
end repeat

–Sort Result (Many->Little)
set sortIndexes to {1} –Key Item id: begin from 0
set sortOrders to {false} –Descending Sort Order
set sortTypes to {"compare:"}
set rList to (current application’s SMSFord’s subarraysIn:cList sortedByIndexes:sortIndexes ascending:sortOrders sortTypes:sortTypes |error|:(missing value)) as list

return rList

–> {{"English", 338}, {"Japanese", 148}, {"German", 135}, {"French", 130}, {"Italian", 98}, {"Spanish", 95}, {"Chinese@China", 91}, {"Dutch", 77}, {"Russian", 70}, {"Korean", 67}, {"Swedish", 64}, {"Chinese@Taiwan", 64}, {"Danish", 62}, {"Portuguese@Brazil", 62}, {"Polish", 60}, {"Finnish", 56}, {"Norwegian", 49}, {"Czech", 48}, {"Setswana", 46}, {"Hungarian", 44}, {"Greek", 43}, {"Romanian", 42}, {"Slovak", 42}, {"Portuguese@Portugal", 41}, {"Croatian", 40}, {"Thai", 40}, {"Catalan", 39}, {"Ukrainian", 39}, {"Arabic", 36}, {"Arabic", 36}, {"Hebrew", 36}, {"Bulgarian", 8}, {"Filipino", 4}, {"Gujarati@India", 3}, {"Amharic@Ethiopia", 2}, {"Spanish@Latin America", 1}, {"Chinese@HongKong", 1}, {"Asturian", 1}, {"Kazakh@Kazakhstan", 1}, {"Zulu, Bantu language spoken in South Africa", 1}, {"Luganda", 1}, {"Danish@Denmark", 1}}

★Click Here to Open This Script