Archive for the 'NSSortDescriptor' Category

2017/01/07 iTunesライブラリの曲のアーティスト名を集計

iTunesライブラリ中に入っている「曲」(song)のアーティスト名を集計するAppleScriptです。

iTunesLibraryフレームワークを用いてライブラリにアクセスしているので、iTunes.appが起動している必要はありません。

アーティスト名については、ライブラリ中にかなりイレギュラーなデータが存在しているため、対策が必要でした。

・初期のiTunesで、CDからリッピングしたものの、CDDBに登録がなかったため、アーティスト名などが登録されていない→ エラートラップで対処

・アーティスト名で姓(last name)と名(first name)の間に空白が入っていたりいなかったりするなど、表記ゆらぎが存在していたため、ゆらぎを吸収

もう少し高速に実行できてもよさそうですが、ライブラリ中のtrackが8,100程度のときに、10回実行時の平均は2.8秒程度です(MacBook Pro Retina 2012)。

AppleScript名:iTunesライブラリの曲のアーティスト名を集計
– Created 2017-01-07 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “iTunesLibrary”
–http://piyocast.com/as/archives/4379

set library to current application’s ITLibrary’s libraryWithAPIVersion:“1.0″ |error|:(missing value)
if library is equal to missing value then return

set allTracks to library’s allMediaItems()
set allCount to allTracks’s |count|()

set anEnu to allTracks’s objectEnumerator()
set newArray to current application’s NSMutableArray’s alloc()’s init()

repeat
  set aPL to anEnu’s nextObject()
  
if aPL = missing value then exit repeat
  
try
    set aKind to (aPL’s mediaKind) as integer
    
    
if (aKind as integer) is equal to 2 then –Music, Song
      set plName to aPL’s artist’s |name| as string
      
set pl2Name to (my changeThis:” “ toThat:“” inString:plName) –日本語アーティスト名で姓と名の間にスペースが入っているものがある(表記ゆらぎ)ので対策
      
newArray’s addObject:(pl2Name)
    end if
  on error
    set aLoc to (aPL’s location’s |path|()) as string
    
log aLoc
  end try
end repeat

set aRes to countItemsByItsAppearance(newArray) of me
–>  {{theName:”浜田省吾”, numberOfTimes:442}, {theName:”B’z”, numberOfTimes:379}, {theName:”渡辺岳夫・松山祐士”, numberOfTimes:199}, {theName:”VariousArtists”, numberOfTimes:192}, {theName:”菅野よう子”, numberOfTimes:108}, {theName:”布袋寅泰”, numberOfTimes:100}, {theName:”三枝成彰”, numberOfTimes:95}, {theName:”宇多田ヒカル”, numberOfTimes:94}, {theName:”宮川泰”, numberOfTimes:81}, {theName:”MichaelJackson”, numberOfTimes:78}, {theName:”稲葉浩志”, numberOfTimes:73}, …

–出現回数で集計
on countItemsByItsAppearance(aList)
  set aSet to current application’s NSCountedSet’s alloc()’s initWithArray:aList
  
set bArray to current application’s NSMutableArray’s array()
  
set theEnumerator to aSet’s objectEnumerator()
  
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
bArray’s addObject:(current application’s NSDictionary’s dictionaryWithObjects:{aValue, (aSet’s countForObject:aValue)} forKeys:{“theName”, “numberOfTimes”})
  end repeat
  
  
–出現回数(numberOfTimes)で降順ソート
  
set theDesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“numberOfTimes” ascending:false
  
bArray’s sortUsingDescriptors:{theDesc}
  
  
return bArray as list
end countItemsByItsAppearance

on changeThis:findString toThat:repString inString:someText
  set theString to current application’s NSString’s stringWithString:someText
  
set theString to theString’s stringByReplacingOccurrencesOfString:findString withString:repString options:(current application’s NSRegularExpressionSearch) range:{location:0, |length|:length of someText}
  
return theString as text
end changeThis:toThat:inString:

★Click Here to Open This Script 

2016/12/14 レコードのうち、最大の値を持つKeyを返す

レコードのうち、最大の値を持つキーを返すAppleScriptです。

AppleScriptからMicrosoftの表情認識APIを呼び出せるようになったはいいものの、得られたデータからどの表情の可能性が高いかを判定する必要があり、そのために書いてみたものです。

Cocoaの機能を用いて、レコードのキーを取り出したり、さまざまな処理を行っています。こうした処理が手軽にできるようになったのは、実にいいことです。

さらに、画像から顔認識を行い、表情を読み取ってデータ化できるというのは、実に素晴らしいと思うものです。

ただ、いろいろ実験を行ってみたところ・・・恐れとか悲しみとか軽蔑といった微妙な感情については検出しづらいことがわかってきました。最大の値のものをピックアップするという方法ではなく、微妙な感情については別途評価を行う必要がありそうです。

mona_lisa_by_leonardo_da_vinci_from_c2rmf_retouched-1.jpg
–> “neutral”

donaldtrump61815_resized.png
–> “anger”

AppleScript名:レコードのうち、最大の値を持つKeyを返す
– Created 2016-12-14 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4359

set aDict to current application’s NSDictionary’s dictionaryWithDictionary:{sadness:0.01133416, anger:1.03975857E-4, happiness:2.90919736E-4, fear:2.28432211E-4, neutral:0.9830475, contempt:2.4698046E-4, disgust:1.02946949E-4, surprise:0.00464509334}
set aMacKey to retMaxValueKey(aDict) of me
–>  ”neutral”

on retMaxValueKey(aDict)
  set aValList to aDict’s allValues()
  
set maxVal to (sort1DNumList(aValList, false) of me)’s firstObject()
  
set keyList to (aDict’s allKeys()) as list
  
  
repeat with i in keyList
    set j to contents of i
    
set aTmp to (aDict’s valueForKey:j)
    
set aRes to compareNumerically(maxVal, aTmp) of me
    
if aRes = true then return j
  end repeat
  
  
return false
end retMaxValueKey

–Numerical Strings Compare
on compareNumerically(aText as text, bText as text)
  set aStr to current application’s NSString’s stringWithString:aText
  
return (aStr’s compare:bText options:(current application’s NSNumericSearch)) = current application’s NSOrderedSame
end compareNumerically

–1D List(数値)をsort / ascOrderがtrueだと昇順ソート、falseだと降順ソート
on sort1DNumList(theList, aBool)
  set theSet to current application’s NSSet’s setWithArray:theList
  
set theDescriptor to current application’s NSSortDescriptor’s sortDescriptorWithKey:(missing value) ascending:aBool
  
set sortedList to theSet’s sortedArrayUsingDescriptors:{theDescriptor}
  
return (sortedList)
end sort1DNumList

★Click Here to Open This Script 

2016/11/02 iTunesライブラリ中の楽曲のジャンルを集計して多い順にソートして出力

iTunesライブラリ中の楽曲のジャンルを集計して多い順にソートして返すAppleScriptです。

集計部分をCocoaで行っているので、集計部分自体が相当に高速なのですが、手元のMacBook Pro Retina 2012(Core i7 2.6GHz)で音声データが6,861件iTunesに登録されている環境で処理したところ、

  アプリケーション(iTunes)を直接呼び出し:3.5秒
  すべてフレームワーク経由で処理:0.03秒

と、アプリケーションを直接操作しないでフレームワーク経由で楽曲の情報を取得して処理するとアプリケーションへの問い合わせを行うよりも100倍以上高速です。

iTunes Music Library.xmlを読み取って処理してもだいたいフレームワーク経由の処理と同じかやや高速なぐらいに落ち着くと思われます。
補足:XML経由の処理はAppleScriptだと(Cocoaの機能を使っても)ムチャクチャ時間がかかりました。参考までに

ただ、処理結果が方法によってほんの微妙に変わるのはジャンル判定処理が異なるためでしょうか。やや、気になります。

GUIアプリ経由で情報を取得するのと、フレームワークを呼び出して情報を取得するのと、XMLを自力で解析するのとでは、提供してくれる機能が違うので、用途に応じて適切なものを取捨選択することになります。「現在再生中の曲」「曲リスト中で選択中のもの」といった情報が必要な場合にはGUIアプリ(iTunes)に問い合わせるのが正解です。

AppleScript名:iTunesライブラリ中の楽曲のジャンルを集計して多い順にソートして出力(アプリケーション呼び出し)
– Created 2016-10-30 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4301

tell application “iTunes”
  –set aList to genre of (every file track whose media kind is equal to song and genre is not equal to “”)
  
set aList to genre of every file track
end tell

set aRes to countItemsByItsAppearance(aList) of me
return aRes
–> {{theName:”サウンドトラック”, numberOfTimes:1721}, {theName:”ロック”, numberOfTimes:942}, {theName:”クラシック”, numberOfTimes:539},

–ジャンルのリストを出現回数で集計
on countItemsByItsAppearance(aList)
  set aSet to current application’s NSCountedSet’s alloc()’s initWithArray:aList
  
set bArray to current application’s NSMutableArray’s array()
  
set theEnumerator to aSet’s objectEnumerator()
  
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
bArray’s addObject:(current application’s NSDictionary’s dictionaryWithObjects:{aValue, (aSet’s countForObject:aValue)} forKeys:{“theName”, “numberOfTimes”})
  end repeat
  
  
–出現回数(numberOfTimes)で降順ソート
  
set theDesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“numberOfTimes” ascending:false
  
bArray’s sortUsingDescriptors:{theDesc}
  
  
return bArray as list
end countItemsByItsAppearance

★Click Here to Open This Script 

AppleScript名:iTunesライブラリ中の楽曲のジャンルを集計して多い順にソートして出力(フレームワーク呼び出し)
– Created 2016-11-02 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “iTunesLibrary”
–http://piyocast.com/as/archives/4301

set library to current application’s ITLibrary’s libraryWithAPIVersion:“1.0″ |error|:(missing value)
if library is equal to missing value then return

set playLists to library’s allPlaylists()
set gArray to library’s allMediaItems()’s genre
set aRes to countItemsByItsAppearance(gArray) of me
–> {{theName:”サウンドトラック”, numberOfTimes:1722}, {theName:”ロック”, numberOfTimes:956}, {theName:”Podcast”, numberOfTimes:755},

–ジャンルのリストを出現回数で集計
on countItemsByItsAppearance(aList)
  set aSet to current application’s NSCountedSet’s alloc()’s initWithArray:aList
  
set bArray to current application’s NSMutableArray’s array()
  
set theEnumerator to aSet’s objectEnumerator()
  
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
bArray’s addObject:(current application’s NSDictionary’s dictionaryWithObjects:{aValue, (aSet’s countForObject:aValue)} forKeys:{“theName”, “numberOfTimes”})
  end repeat
  
  
–出現回数(numberOfTimes)で降順ソート
  
set theDesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“numberOfTimes” ascending:false
  
bArray’s sortUsingDescriptors:{theDesc}
  
  
return bArray as list
end countItemsByItsAppearance

★Click Here to Open This Script 

2016/09/20 連番JPEGファイルを読み込んで連結したPDFを作成(新規作成)

連番JPEG画像を番号順にソートして、順次連結したPDFを新規作成するAppleScriptです。

jpeg_catfiles.jpg

このような連番画像を連結して任意のファイル名のPDFに合成します。出来上がるPDFは、元のJPEGファイルの圧縮度に応じて大きくなります。

新規作成よりも、すでに存在しているPDFにJPEGファイルを連結するほうが実用的だと思います。

AppleScript名:連番JPEGファイルを読み込んで連結したPDFを作成(新規作成)
– Created 2016-09-20 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “QuartzCore”
use framework “Quartz”
use framework “AppKit”

set aExt to “.jpg”
set aFol to choose folder
set fList to getFilePathList(aFol, aExt) of me
set f2List to my sort1DList:fList ascOrder:true –sort by ascending

set newFile to POSIX path of (choose file name with prompt “新規PDFファイルの名称を選択”)
set newFilePath to current application’s NSString’s stringWithString:newFile

–Make Blank PDF
set aPDFdoc to current application’s PDFDocument’s alloc()’s init()

set pageNum to 0

repeat with i in f2List
  set j to contents of i
  
set aURL to (current application’s |NSURL|’s fileURLWithPath:j)
  
set bImg to (current application’s NSImage’s alloc()’s initWithContentsOfURL:aURL)
  (
aPDFdoc’s insertPage:(current application’s PDFPage’s alloc()’s initWithImage:bImg) atIndex:pageNum)
  
set pageNum to pageNum + 1
end repeat

aPDFdoc’s writeToFile:newFilePath

–ASOCで指定フォルダのファイルパス一覧取得(拡張子指定つき)
on getFilePathList(aFol, aExt)
  set aPath to current application’s NSString’s stringWithString:(POSIX path of aFol)
  
set aFM to current application’s NSFileManager’s defaultManager()
  
set nameList to (aFM’s contentsOfDirectoryAtPath:aPath |error|:(missing value)) as list
  
set anArray to current application’s NSMutableArray’s alloc()’s init()
  
  
repeat with i in nameList
    set j to i as text
    
if (j ends with aExt) and (j does not start with “.”) then –exclude invisible files
      set newPath to (aPath’s stringByAppendingString:j)
      (
anArray’s addObject:newPath)
    end if
  end repeat
  
  
return anArray as list
end getFilePathList

–1D List(文字)をsort / ascOrderがtrueだと昇順ソート、falseだと降順ソート
on sort1DList:theList ascOrder:aBool
  set aDdesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“self” ascending:aBool selector:“localizedCaseInsensitiveCompare:”
  
set theArray to current application’s NSArray’s arrayWithArray:theList
  
return (theArray’s sortedArrayUsingDescriptors:{aDdesc}) as list
end sort1DList:ascOrder:

★Click Here to Open This Script 

2016/07/28 Bookフォルダリネーム

書籍の原稿管理用のAppleScriptです。原稿は章ごとにフォルダ分けしており、

 9999 フォルダ名あるいはファイル名

といったように、仮想ノンブルを前に付けています。この仮想ノンブルで前後関係を制御しており、基本的に数字の大小だけが重要です。

whole.png

最後に1冊分PDFにまとめてビルドするAppleScriptで、書き出し対象のフォルダ以下のMarkdownファイルとPagesのファイルをピックアップし、ファイル名でソート。ソート順は仮想ノンブルによってコントロールされることになります。

Finderの並べ替えを使っているので、シンプルでわかりやすく、使い勝手もよいのですが……ただひとつ難点が。

途中にコンテンツを挿入したい場合にも仮想ノンブルでコントロールしつつ、途中に入るように操作するのですが……場合によってはフォルダおよび書類の仮想ノンブル部分だけリネームする必要が出てきます。

とくに、目下改定作業中の「AppleScript最新リファレンス」のコマンドリファレンス部分は、初版から大幅に改定・補充を行っているため、ひんぱんにリネームが発生して大変です(ーー;;

そこで、こんなScriptを組んでリネーム作業だけ自動化してみました。

Finder上でフォルダを選択して本Scriptを実行すると、選択中のフォルダ名から仮想ノンブルの番号を取得し、

before2.png

内包するフォルダに入っているファイルについても、すべて仮想ノンブル部分を書き換えます。

after2.png

AppleScript名:Bookフォルダリネーム
– Created 2016-07-28 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions
# http://piyocast.com/as/archives/4177

set aStep to 10

tell application “Finder”
  set aSel to (selection as alias list)
end tell
if aSel = {} or aSel = “” then return
set aFol to first item of aSel

tell application “Finder”
  –選択中のフォルダの名称を取得
  
set aName to name of aFol
  
  
–選択中のフォルダに入っている書類を取得
  
tell folder (aFol as string)
    set fList to name of every file as alias list
  end tell
end tell

set {startNumStr, folderNameStr} to parseNumAndString(aName) of me
set startNum to startNumStr as integer

set aRes to sort1DListOrder(fList, true) of me –昇順でファイル名をソート

set aFolStr to aFol as string
set aCount to startNumStr
tell application “Finder”
  repeat with i in aRes
    set anAlias to (aFolStr & (i as string)) as alias
    
set tmpName to name of anAlias
    
    
set {curNum, curName} to parseNumAndString(tmpName) of me
    
set newName to (retZeroPaddingText(aCount, length of curNum) of me) & ” “ & curName
    
    
set name of anAlias to newName
    
    
set aCount to aCount + aStep
  end repeat
end tell

–文字列から先頭にあるとおぼしき数値と、スペースで区切られた後続の文字列を分離する
on parseNumAndString(aName)
  set aOffset to offset of ” “ in aName
  
if aOffset = 0 then error “Illigal Format”
  
set aNum to text 1 thru (aOffset - 1) of aName
  
set aText to text (aOffset + 1) thru -1 of aName
  
return {aNum, aText}
end parseNumAndString

–1D Listをsort / ascOrderがtrueだと昇順ソート、falseだと降順ソート
on sort1DListOrder(theList, aBool)
  set aDdesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“self” ascending:aBool selector:“localizedCaseInsensitiveCompare:”
  
set theArray to current application’s NSArray’s arrayWithArray:theList
  
return (theArray’s sortedArrayUsingDescriptors:{aDdesc}) as list
end sort1DListOrder

on retZeroPaddingText(aNum as integer, aDigitNum as integer)
  if aNum > (((10 ^ aDigitNum) as integer) - 1) then return “” –Range Check
  
set aFormatter to current application’s NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setUsesGroupingSeparator:false
  
aFormatter’s setAllowsFloats:false
  
aFormatter’s setMaximumIntegerDigits:aDigitNum
  
aFormatter’s setMinimumIntegerDigits:aDigitNum
  
aFormatter’s setPaddingCharacter:“0″
  
set aStr to aFormatter’s stringFromNumber:(current application’s NSNumber’s numberWithFloat:aNum)
  
return aStr as string
end retZeroPaddingText

★Click Here to Open This Script 

2016/06/18 指定フォルダ以下にあるMacDownとPages書類をソートしてPDFに書き出す

指定フォルダ以下にあるMacDownで記述したMarkdown書類と、Pages書類をすべての階層からピックアップしてファイル名でソートして、すべてデスクトップにPDFで書き出すAppleScriptです。

コンパイル(構文確認)および実行に際しては、Shane StanleyのScript Library「Bridge Plus」をインストールしておく必要があります。また、GUI Scriptingを利用しているため「システム環境設定」>「セキュリティとプライバシー」>「プライバシー」>「アクセシビリティ」でスクリプトエディタ(アプレットとして実行する場合にはアプレットそのもの)を登録して許可しておく必要があります。

「技術書典」に出す電子ブックのフォーマットがギリギリまで決まらず、しかも縦長のスクロールさせるタイプのものにできないかとあがいていたのですが、結局iPadあたりで読むことを考慮するとiPadのリーダーの仕様にしたがう必要があります。

……あれ?(^ー^; 結局、ページめくりは発生するし、一般的な本と同じような体裁になってしまいますよ → フォーマットがPDFになりました。

Markdown書類とPages書類が混在しているフォルダ構造のトップ階層のフォルダを指定すると、Markdown書類とPages書類をピックアップし、ファイル名でそれらをソートし、順次PDFに書き出すAppleScriptを書いてみました(必要は発明のマザー!)。

book1.png

ただ、MacDownには「書類をPDFに書き出す」という機能がAppleScript側に公開されていません(T_T)。

macdown_dict.png

ないものを「ないない」と嘆いても仕方がないので、さっさとGUI Scriptingで強制的にメニュー操作することにしました。

macdown_gui.png

で、どこに? どこに保存させるのでしょう??

大丈夫! そんなときには、保存ダイアログで幾つかのフォルダに強制的に移動させるキーボードショートカットが存在しており、Command-Dは「カレントディレクトリをデスクトップに移動させる」=「保存先をデスクトップにする」働きをします。

このため、保存先を操作しづらい(不可能とはいいませんけれども)GUI Scriptingにおいて保存先を指定することが、デスクトップフォルダについては可能になっています。

Pagesの方はひじょうに素直に(GUI Scriptingなんて使わずに)PDF書き出しが可能です。

そんなわけで、時間に追い詰められながらもなんとか大量のデータ処理を行っているのでありました。書き出した大量のPDFもAppleScriptでさくっと連結できるので、非常にいい感じです。あとは、本が完成すれば、、、、

AppleScript名:指定フォルダ以下にあるMDとPagesをソートしてPDFに書き出す
– Created 2016-06-17 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”
–use spotLight : script “spotlightLib”

property searchRes : {}

load framework

set origPath to POSIX path of (choose folder with prompt “Markdown/Pages ファイルの入っているフォルダを選択”)

set aResList to (spotlightSearch(origPath, “kMDItemKind == ’Markdown’ || kMDItemKind == ’Pages 一般書類’”) of me) as list –Caution! this parameter is *localized*

–フルパスとファイル名のペアの2D Listを作成
set newList to {}
repeat with i in aResList
  set j to contents of i
  
set aStr to (current application’s NSString’s stringWithString:j)
  
set aFileName to aStr’s lastPathComponent()
  
set the end of newList to {aStr, aFileName}
end repeat

–番号順にソート
set sortIndexes to {1} –Key Item id: begin from 0, Sort by filename
set sortOrders to {true}
set sortTypes to {“compare:”}
set resList to (current application’s SMSForder’s subarraysIn:(newList) sortedByIndexes:sortIndexes ascending:sortOrders sortTypes:sortTypes |error|:(missing value)) as list
if resList = {} then return –No Result

–ソートした順番にMarkdownファイル/Pages書類ファイルをオープンしてデスクトップにPDF生成してクローズ
repeat with i in newList
  copy i to {fullPath, aFileName}
  
  
set apFile to (POSIX file (fullPath as string))
  
set anAlias to apFile as alias
  
set aFileName to aFileName as string
  
  
if aFileName ends with “.md” then
    exportFromMacDown(anAlias) of me –Markdown
  else if aFileName ends with “.pages” then
    exportFromPages(anAlias) of me –Pages
  end if
end repeat

–指定のPagesファイル(alias)をデスクトップ上にPDFで書き出し
on exportFromPages(anAlias)
  tell application “Finder”
    set aName to name of anAlias
  end tell
  
  
set dtPath to (path to desktop) as string
  
set outPath to dtPath & aName & “.pdf”
  
  
tell application “Pages”
    close every document without saving
    
open anAlias
    
export document 1 to file outPath as PDF with properties {image quality:Best}
    
close every document without saving
  end tell
end exportFromPages

–指定のMacDownファイル(alias)をデスクトップ上にPDFで書き出し
on exportFromMacDown(anAlias)
  tell application “MacDown”
    open {anAlias}
  end tell
  
  
tell current application
    delay 1 –ここの時間待ちが少ないと画像抜けが発生?
  end tell
  
macDownForceSave() of me
  
  
tell application “MacDown”
    close every document without saving
  end tell
end exportFromMacDown

–注意!! ここでGUI Scriptingを使用。バージョンが変わったときにメニュー階層などの変更があったら書き換え
on macDownForceSave()
  activate application “MacDown”
  
tell application “System Events”
    tell process “MacDown”
      – File > Export > PDF
      
click menu item 2 of menu 1 of menu item 14 of menu 1 of menu bar item 3 of menu bar 1
      
      
–Go to Desktop Folder
      
keystroke “d” using {command down}
      
      
–Save Button on Sheet
      
click button 1 of sheet 1 of window 1
    end tell
  end tell
end macDownForceSave

–Spotlight Libの内容を引っ張り出してきた
on spotlightSearch(origPOSIXpath, aCondition)
  
  
set searchRes to {} –initialize
  
  
initiateSearchForFullPath(aCondition, origPOSIXpath) –Predicate & Scope Directory
  
  
–Waiting for the result
  
repeat while searchRes = {}
    current application’s NSThread’s sleepForTimeInterval:(“0.001″ as real) –delay 0.001
  end repeat
  
  
set anObj to searchRes’s firstObject() –Pick up the first one for test
  
if anObj = missing value then return {} –No Result
  
  
–set anAttrList to anObj’s attributes() –”mdls” attributes
  
–>  (NSArray) {”kMDItemContentTypeTree”, “kMDItemContentType”, “kMDItemPhysicalSize”, …}
  
  
set resArray to {}
  
repeat with anItem in my searchRes
    set j to contents of anItem
    
set aPath to (j’s valueForAttribute:“kMDItemPath”) as string
    
set the end of resArray to aPath
  end repeat
  
  
return resArray
end spotlightSearch

on initiateSearchForFullPath(aQueryStrings, origPath)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
aSearch’s setPredicate:aPredicate
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{origPath}
  
aSearch’s setSearchScopes:aScope
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemFSName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
aSearch’s startQuery()
  
end initiateSearchForFullPath

on queryDidUpdate:sender
  –  
end queryDidUpdate:

on initalGatherComplete:sender
  set anObject to sender’s object
  
anObject’s stopQuery()
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
set my searchRes to anObject’s results()
end initalGatherComplete:

★Click Here to Open This Script 

2016/06/17 ASOCでmdfindするじっけん v4(フルパスを返す)

Spotlightの機能をCocoa経由で呼び出してmdfindコマンドのクエリーと検索対象フォルダをPOSIX pathで与えると、条件に合致するファイルのPOSIX pathをリストに入れて返してくるAppleScriptです。

以前に掲載したバージョンを実際に使おうとしたら、激しく間違っていることを発見。なんで気がつかなかったんでしょう。いえ、なんでこんな忙しいときに気づいてしまうんでしょう。

AppleScript名:ASOCでmdfindするじっけん v4(フルパスを返す)
– Created 2016-06-17 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

property searchRes : {}

set origPath to POSIX path of (choose folder)
set aList to spotlightSearch(origPath, “kMDItemContentType == ’net.daringfireball.markdown’”) of me

on spotlightSearch(origPath, aMDfindParam)
  
  
set my searchRes to {} –initialize
  
initiateSearchForFullPath(aMDfindParam, origPath) –Predicate & Scope Directory
  
  
–Waiting for the result
  
repeat while my searchRes = {}
    delay 0.01
  end repeat
  
  
set anObj to my searchRes’s firstObject() –Pick up the first one for test
  
if anObj = missing value then return {} –No Result
  
  
set resArray to {}
  
repeat with anItem in my searchRes
    set j to contents of anItem
    
set aPath to (j’s valueForAttribute:“kMDItemPath”) as string
    
set the end of resArray to aPath
  end repeat
  
  
return resArray
  
end spotlightSearch

on initiateSearchForFullPath(aQueryStrings, origPath)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
aSearch’s setPredicate:aPredicate
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{origPath}
  
aSearch’s setSearchScopes:aScope
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemFSName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
aSearch’s startQuery()
  
end initiateSearchForFullPath

on queryDidUpdate:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0×7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = }
end queryDidUpdate:

on initalGatherComplete:sender
  set anObject to sender’s object
  
anObject’s stopQuery()
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
  
set my searchRes to anObject’s results()
end initalGatherComplete:

★Click Here to Open This Script 

2016/02/28 Safariの履歴を読んでトップ10をテキストで出力する

SafariのHistory.plistを読み込んで、アクセス回数のトップ10をテキスト出力するAppleScriptです。

Safari 9.1+OS X 10.11環境を前提としています。plistの内容については、Safariのバージョンごとに変更が加わってもおかしくないので、メジャーバージョンアップ時には動作確認する必要があることでしょう。

他のアプリケーションの.plistファイルにアクセスするのはかなり野蛮な処理ですが(Sandbox環境では許可されない)、場合によっては有用なこともあるでしょう。

ちなみに、Scriptの実行例はサブマシン(MacBook Air 2011)で実行したものです。

AppleScript名:Safariの履歴を読んでトップ10をテキストで出力する
– Created 2016-02-27 by Takaaki Naganoya
– Piyomaru Software 2016
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set libPath to (POSIX path of (path to library folder from user domain)) & “Safari/History.plist”
set aRec to retDictFromPlist(libPath) of me
set bRecList to aRec’s valueForKeyPath:“WebHistoryDates”

set bArray to sortRecListByLabel(bRecList, “visitCount”, false) of me
set outStr to “”
set aCount to 0

repeat with i from 0 to 100
  set anItem to (bArray’s objectAtIndex:i)
  
  
set aURL to (anItem’s valueForKeyPath:“”) as string
  
set aTitle to (anItem’s valueForKeyPath:“title”) as string
  
set aDispTItle to (anItem’s valueForKeyPath:“displayTitle”) as string
  
set aVisitCount to (anItem’s valueForKeyPath:“visitCount”) as string
  
set redirectURLs to (anItem’s valueForKeyPath:“redirectURLs”) as list
  
set lastVisitedDate to (anItem’s valueForKeyPath:“lastVisitedDate”) as string
  
  
if aURL does not start with “http://piyocast.com” then –自分のところのサイト関連を除外
    set aStr to ((aCount + 1) as string) & “:(” & (aVisitCount as string) & “)” & aTitle & return
    
set outStr to outStr & aStr
    
    
set aCount to aCount + 1
    
if aCount = 10 then exit repeat
    
  end if
  
  
end repeat

return outStr

(*
–>  ”1:(7657)radiko.jp
2:(608)Apple
3:(416)ソフトバンクWi-Fiスポット
4:(156)公式Apple Store(日本)- iPad Air、MacBook Pro、ほかにもたくさん
5:(125)Yahoo! JAPAN
6:(108)Mac整備済製品 - Apple Store (Japan)
7:(54)Apple-Style \”アップルなBlogをリンク\”
8:(44)PC Watch
9:(31)Facebook
10:(28)アップル - Mac Pro

*)

–Read plist
on retDictFromPlist(aPath as text)
  set thePath to current application’s NSString’s stringWithString:aPath
  
set thePath to thePath’s stringByExpandingTildeInPath()
  
set theDict to current application’s NSDictionary’s dictionaryWithContentsOfFile:thePath
  
return theDict
end retDictFromPlist

–リストに入れたレコードを、指定の属性ラベルの値でソート
on sortRecListByLabel(aRecList, aLabelStr, ascendF)
  set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
set sortDesc to current application’s NSSortDescriptor’s alloc()’s initWithKey:aLabelStr ascending:ascendF
  
set sortDescArray to current application’s NSArray’s arrayWithObjects:sortDesc
  
set sortedArray to aArray’s sortedArrayUsingDescriptors:sortDescArray
  
return sortedArray
end sortRecListByLabel

★Click Here to Open This Script 

2015/09/05 ASOCでspotlight検索するじっけん v2

Cocoaの機能を用いてspotlight検索を行うAppleScriptです。Script Editor上でControl-Command-RでForeground実行する必要があります。

前バージョンでは、ファイル名しか取得できていませんでしたが、検索でヒットしたアイテムのフルパスを返すようにできたので、ようやくこれで実用レベルに達したと思います。

(1)検索対象フォルダを指定
(2)検索条件を設定

すると、実際にspotlight検索を行って結果を返します。

検索結果待ちループ側でdelayを極小にできるよう、試行錯誤してみましたが、

(1)delayを削除するとうまく動かない
(2)delay 0.01のとおりに本当に0.01秒待っているかはわからない
(3)手元の環境ではうまく動いているが、環境に応じたチューニングが必要?(2台で確認)

という印象を持っています。

ASOCで0.001という数値をプログラム内に記述するのにえっらい苦労しました。そのまま書くと指数表現されてしまい、Pure AppleScriptの世界ではエラーにならないんですが、Cocoaのオブジェクトに渡されるときにうまくブリッジされないのか、エラーに。NSSNumberでfloatValueを設定しようとしてもうまく行かないわで、結局、文字列で書いて強制的にcastするという強引な方法で解決。もっといい方法があるとよいのですが、相当苦労しました。

いろいろ条件を変えて検索の実験をしたところ、与えた検索条件でヒットがない場合には早々に{}を返してくるため、無限ループで処理が終わらないということはないようです。

これでもう、spotlight検索のためにshell commandのmdfindを呼び出す必要はなくなりました。

AppleScript Users MLの過去ログや、ShaneのEveryday AppleScriptObjC添付のサンプルコード、ASObjC Explorer 4添付のサンプルコードなども探し回ってspotlight検索のサンプルを探してみましたが、検索結果のフルパスを返すものはなかったので(ついでに言うとObjective-Cのプログラムを検索エンジンで探しても、けっこう見つからない)、これを書いておいた意義はあるものと思います。

AppleScript名:ASOCでmdfindするじっけん v2(フルパスを返す)
– Created 2015-09-03 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

property searchRes : {}

set searchRes to {} –initialize

set origPath to POSIX path of (choose folder)
initiateSearchForFullPath(“kMDItemDisplayName == ’ICON0.PNG’”, origPath) –Predicate & Scope Directory

–Waiting for the result
repeat while searchRes = {}
  current application’s NSThread’s sleepForTimeInterval:(“0.001″ as real) –delay 0.001
end repeat

set anObj to searchRes’s firstObject() –Pick up the first one for test
if anObj = missing value then return {} –No Result

–set anAttrList to anObj’s attributes() –”mdls” attributes
–>  (NSArray) {”kMDItemContentTypeTree”, “kMDItemContentType”, “kMDItemPhysicalSize”, …}

set resArray to current application’s NSMutableArray’s alloc()’s init()
repeat with anItem in searchRes
  (resArray’s addObject:(anObj’s valueForAttribute:“kMDItemPath”))
  
–>  (NSString) “/Users/me/Documents/機動戦士ガンダム戦場の絆 Folder/ULJS00181USERID_0000/ICON0.PNG”
end repeat

resArray
–>  (NSArray) {”/Users/me/Documents/機動戦士ガンダム戦場の絆 Folder/ULJS00181USERID_0000/ICON0.PNG”,……}

on initiateSearchForFullPath(aQueryStrings, origPath)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
aSearch’s setPredicate:aPredicate
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{origPath}
  
aSearch’s setSearchScopes:aScope
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemFSName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
aSearch’s startQuery()
  
end initiateSearchForFullPath

on queryDidUpdate:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0×7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = }
end queryDidUpdate:

on initalGatherComplete:sender
  set anObject to sender’s object
  
anObject’s stopQuery()
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
  
set my searchRes to anObject’s results()
end initalGatherComplete:

★Click Here to Open This Script 

2015/08/31 ASOCでspotlight検索するじっけん

Cocoaの機能を用いてspotlightによる検索を行うAppleScriptの実験的な実装です。

オリジナルは、Appleのオンラインドキュメント「File Metadata Search Programming Guide」にあった、「Listing 2-1 Static Spotlight search implementation」です。これをAppleScriptObjCに(少しアレンジを加えつつ)翻訳したものです。

Script Editor上でCommand-Control-RによるForeground実行する必要があります。プログラムリスト中におけるログ内容はASObjC Explorer 4で表示させたもので、同様の内容を確認したい場合にはASObjC Explorer 4が必要です。

海外のユーザーから「どうやって表示してんの、それ?(Cocoa Objectのログ)」と、問い合わせがあって「通常のScript Editorでは表示できないよ」と回答。意外と知られていないようで、、、(もったいないので、ここで宣伝しておくことにしました)。

ASObjC Explorer 4のログ表示は、AppleScriptのプログラムの間にログ表示用のコードを挟んでいるとのことなので、必ずしも実際のオブジェクトとイコールの内容が表示されているわけではありませんが、実際の内容をつかみやすいものになっています。この機能を持つAppleScript系のエディタは、目下のところASObjC Explorer 4だけです。

Objective-CのプログラムをさらっとAppleScriptObjCのプログラムに書き換えできるのは、ASObjC ExplorerのCocoaオブジェクトログ機能があればこそです。ログを出しつつ属性値を確認できるため、おおいに役立っています。逆に、Cocoaオブジェクトログ機能がないと、全く歯が立ちません(AppleがShaneのプログラムを買い取って、標準のScript Editorに機能をマージすればいいのに、、、)。

AppleScript名:ASOCでspotlight検索するじっけん
– Created 2015-08-31 by Takaaki Naganoya
– 2015 Piyomaru Software

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

property searchRes : {}

set searchRes to {} –initialize
initiateSearch(“kMDItemContentTypeTree == ’public.image’”)

–Waiting for the result
repeat while searchRes = {} –Inifinity loop, danger!!
  delay 0.1
end repeat

set anObj to searchRes’s firstObject() –Pick up the first one for test
set anAttrList to anObj’s attributes() –”mdls” attributes
–>  (NSArray) {”kMDItemContentTypeTree”, “kMDItemContentType”, “kMDItemPhysicalSize”, …}

set aFileName to anObj’s valueForKey:“kMDItemDisplayName”
–>  (NSString) ” 1 - 95.7.16.jpg” — example

on initiateSearch(aQueryStrings)
  
  
set aSearch to current application’s NSMetadataQuery’s alloc()’s init()
  
–>  (NSMetadataQuery)
  
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“queryDidUpdate:” |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:aSearch
  
current application’s NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:“initalGatherComplete:” |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aQueryStrings
  
–> (NSComparisonPredicate) kMDItemContentTypeTree == “public.image”
  
aSearch’s setPredicate:aPredicate
  
  
  
set aScope to current application’s NSArray’s arrayWithObjects:{current application’s NSMetadataQueryUserHomeScope, current application’s NSMetadataQueryUbiquitousDocumentsScope}
  
–> (NSArray) {{”kMDQueryScopeHome”, “NSMetadataQueryUbiquitousDocumentsScope”}
  
aSearch’s setSearchScopes:aScope
  
  
  
set sortKeys to current application’s NSSortDescriptor’s sortDescriptorWithKey:“kMDItemDisplayName” ascending:true
  
aSearch’s setSortDescriptors:(current application’s NSArray’s arrayWithObject:sortKeys)
  
  
  
aSearch’s startQuery()
  
end initiateSearch

on queryDidUpdate:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0×7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = }
end queryDidUpdate:

on initalGatherComplete:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0×7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = }
  
  
set anObject to sender’s object
  
anObject’s stopQuery()
  
  
set aRes to anObject’s resultCount()
  
–>  173352 –different in each environment. Just a test
  
  
set my searchRes to anObject’s results()
  
–>  (NSArray) {(NSMetadataItem) , (NSMetadataItem) , ……
  
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidUpdateNotification) object:anObject
  
current application’s NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(current application’s NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
  
set anObject to missing value –really needed?
  
end initalGatherComplete:

★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/11/26 1D Listを文字列長でソート v2

1D Listの文字列長によるソートの改良版です。文字列長に着目してソートする場合には、同一長の文字列がソートされている必要はとくにないのですが、同一文字列長の場合にソートを行っています。

1D Listの文字列長ソートを掲載していたら、Shane Stanleyからツッコミが。

「それ、間違いじゃないけど文字列長でソートして、同時に(文字の)ソート順を指定しておいた方がいいよ(意訳)」

たしかに。文字列長でソートする用途しか考えていなかったので、文字列順でソートされていることは重視していませんでしたが、文字列順にソートされていた方がいい感じです。

文字列長でソートする用途というのは・・・Mail.appでSubjectに含まれるキーワードでサブフォルダに仕分けを行うさいに、文字列の長いものを優先して(長い→短い の順に)フォルダとの照合を行うロジックをAppleScriptで作って(&使って)いるためです。

このロジックを使用しているため、「Mountain Lion」のほうが「Lion」よりも優先して処理されることになり、(ほぼ)思い通りの処理をおこなえています。

US AppleがホスティングしているMailing Listにほぼすべて入っており(いくつか、US現地の教育関係者と政府関係者しか入れないMLからは丁重に追い出されましたが)、MLのログをこのようにキーワードごとに細分化したサブフォルダ(複数キーワードをカンマで区切って、類義語を列挙する仕様)に再振り分けしています。

mails.png

AppleScript名:1D Listを文字列長でソート v2
– Created 2014-11-25 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aList to {“Apple”, “Orange”, “Banana”, “Meron”, “Strawberry”, “Lemon”, “Takaaki Naganoya”, “Piyomaru Software”}
set bList to sort1DListByStringLength(aList as list, true) of me –昇順
–> {”Apple”, “Meron”, “Lemon”, “Orange”, “Banana”, “Strawberry”, “Takaaki Naganoya”, “Piyomaru Software”}–v1
–> {”Apple”, “Lemon”, “Meron”, “Banana”, “Orange”, “Strawberry”, “Takaaki Naganoya”, “Piyomaru Software”}–v2

set cList to sort1DListByStringLength(aList as list, false) of me –降順
–> {”Piyomaru Software”, “Takaaki Naganoya”, “Strawberry”, “Orange”, “Banana”, “Apple”, “Meron”, “Lemon”}–v1
–> {”Piyomaru Software”, “Takaaki Naganoya”, “Strawberry”, “Banana”, “Orange”, “Apple”, “Lemon”, “Meron”}–v2

–1D Listを文字列長でソート v2
on sort1DListByStringLength(aList as list, sortOrder as boolean)
  set aArray to current application’s NSArray’s arrayWithArray:aList
  
set desc1 to current application’s NSSortDescriptor’s sortDescriptorWithKey:“length” ascending:sortOrder
  
set desc2 to current application’s NSSortDescriptor’s sortDescriptorWithKey:“self” ascending:true selector:“localizedCaseInsensitiveCompare:”
  
set bArray to aArray’s sortedArrayUsingDescriptors:{desc1, desc2}
  
set bList to (bArray’s ASify()) as list
  
return bList
end sort1DListByStringLength

★Click Here to Open This Script 

2014/11/25 1D Listをユニーク化してソート

1D Listをユニーク化してソートするAppleScriptです。

以前に、いろいろ1D Listのユニーク化の方法について検討していましたが、処理後のソート状態が「不定」であるため、決め手に欠けるみたいな話になっていました。

ただ、ソート状態が「不定」なら、明示的にソートすればいいだけの話で、そのように処理を足してやりました。

AppleScript名:1D Listをユニーク化してソート
– Created 2014-11-25 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "ASObjCExtras"

set aList to {100, 10, 1, 90, 300, 300}

set aRes to uniquifyAndSort1DList(aList, true)
–> {1, 10, 90, 100, 300}

–1D Listをユニーク化してソート
on uniquifyAndSort1DList(theList as list, aBool as boolean)
  
  
set aArray to current application’s NSArray’s arrayWithArray:theList
  
set bArray to aArray’s valueForKeyPath:"@distinctUnionOfObjects.self"
  
set aDdesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:"self" ascending:aBool selector:"compare:"
  
set cArray to bArray’s sortedArrayUsingDescriptors:{aDdesc}
  
  
set bList to (cArray’s ASify()) as list
  
  
return bList
end uniquifyAndSort1DList

★Click Here to Open This Script 

2014/11/25 1D Listを文字列長でソート

文字列だけで構成された1D Listを、各要素の文字列の長さに注目してソートするAppleScriptです。

以前に作成したルーチンのASOC版です。ただ・・・一般的には1D Listだけで処理するなんてことはほとんどなく、いくつかのデータを一緒にした2D Listで処理することになります。

2D Listの文字列長ソートも書いて(掲載しかけ)たのですが、ASObjCExtras.frameworkが2D Listのソート時に「length」でのソートをサポートしていないことがFrameworkのヘッダーファイルに書いてあったので、おクラ入りしました。残念!

AppleScript名:1D Listを文字列長でソート
– Created 2014-11-25 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aList to {“Apple”, “Orange”, “Banana”, “Meron”, “Strawberry”, “Lemon”, “Takaaki Naganoya”, “Piyomaru Software”}
set bList to sort1DListByStringLength(aList as list, true) of me –昇順
–> {”Apple”, “Meron”, “Lemon”, “Orange”, “Banana”, “Strawberry”, “Takaaki Naganoya”, “Piyomaru Software”}

set cList to sort1DListByStringLength(aList as list, false) of me –降順
–> {”Piyomaru Software”, “Takaaki Naganoya”, “Strawberry”, “Orange”, “Banana”, “Apple”, “Meron”, “Lemon”}

–1D Listを文字列長でソート
on sort1DListByStringLength(aList as list, sortOrder as boolean)
  set aArray to current application’s NSArray’s arrayWithArray:aList
  
set descs to current application’s NSArray’s arrayWithObject:(current application’s NSSortDescriptor’s sortDescriptorWithKey:“length” ascending:sortOrder)
  
  
set bArray to aArray’s sortedArrayUsingDescriptors:descs
  
set bList to (bArray’s ASify()) as list
  
return bList
end sort1DListByStringLength

★Click Here to Open This Script 

2014/11/21 レコードのリストをソート(asoc)

レコードのリストをソートするAppleScriptです。

{{aName:”piyoko”, aVal:100}, {aName:”piyomaru”, aVal:80}, {aName:”piyoo”, aVal:10}}

のようなレコードをリスト化したデータに対して、レコードの属性ラベルを指定してその値でソートを行います。

AppleScript名:asoc_レコードのリストをソート
– Created 2014-11-21 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aRecList to {{aName:“piyoko”, aVal:100}, {aName:“piyomaru”, aVal:80}, {aName:“piyoo”, aVal:10}}

set bList to sortRecListByLabel(aRecList, “aVal”, true) of me –昇順ソート
–> {{aName:”piyoo”, aVal:10}, {aName:”piyomaru”, aVal:80}, {aName:”piyoko”, aVal:100}}

set bList to sortRecListByLabel(aRecList, “aVal”, false) of me –降順ソート
–> {{aName:”piyoko”, aVal:100}, {aName:”piyomaru”, aVal:80}, {aName:”piyoo”, aVal:10}}

–リストに入れたレコードを、指定の属性ラベルの値でソート
on sortRecListByLabel(aRecList, aLabelStr, ascendF)
  set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
  
set sortDesc to current application’s NSSortDescriptor’s alloc()’s initWithKey:aLabelStr ascending:ascendF
  
set sortDescArray to current application’s NSArray’s arrayWithObjects:sortDesc
  
set sortedArray to aArray’s sortedArrayUsingDescriptors:sortDescArray
  
  
set bList to (sortedArray’s ASify()) as list
  
return bList
end sortRecListByLabel

★Click Here to Open This Script 

2014/10/30 ASからASOCのハンドラを呼び出す際には

昨日の問題の解決・・・小数を含む数値のリストをASOC(「AppleScriptObjC」あるいは「AppleScript/Objective-C」と呼ばれる)のハンドラに渡してソートさせた場合に、小数を含む値に丸め誤差が発生するという問題についてです。

これまでにも、Xcode上でASOCのプログラム(GUIベースのアプリケーション)は作ってきましたが、ASOCのハンドラ同士で呼び出しを行う際には極力文字列でデータをやりとりし、AppleScript側からASOC側を呼び出すことはしてきませんでした。

OS X 10.9までは、スクリプトエディタ上でもASOCのプログラムは書けたものの、あくまでアプレット上で完全にプログラムが分断された状態で動いていたために、見えてこなかった問題点でもあります。

つまり、この問題はOS X 10.10で1本のScript中で通常のAppleScriptとASOCが混在する状況が生まれたために見えてきた問題ともいえます(個人的に、OS X 10.9までのScript Editor上で記述するASOC(Interface BuilderでGUIが作れない)にあまり魅力を感じていなかったので、全然使っていなかったし)。

xcode.png

# Xcode上でも「小数点を含む数値リスト」を処理した場合には、同様の問題が出ました。Scripting Bridgeそのもののバグっぽいですね。Appleにバグレポートを書いて担当者を説得するにも時間がかかるので、目下解決策を検証中です。解決はしたのですが、自分ひとりだけでは済まないので、、、

調査したところ、list→NSArrayの型変換では問題は発生していないようでしたが、ASOCのハンドラからNSArray→listの型変換を行って値を返す際に問題が発生していました。

NSArray→listの型変換そのものはScripting Bridgeの処理系が行っているため、現状ではどーーにもなりません(Scripting Bridgeのバグ)が、「数値リスト」ではなく「数字のテキストのリスト」を扱うようにすれば、数値リストの型変換にともなう丸め誤差の発生を回避できます。

つまり、数値文字列のリストをASOC側に渡して、ソーティング(大小比較)自体を「数値として評価」すればOK。

というわけで、ASOC側には「数値リスト」ではなく「数字の文字列リスト」を渡し、ASOC側でそれぞれの要素を「数値として大小比較」するようにしてみました。念のため、有効桁数の多いfloatValueとして数値評価させています。

だいたい、AppleScriptObjCでプログラムを組む場合には、User Interface系をAppleScriptObjCで実装し、メインロジック部分は別途「通常のAppleScript」で組むことで、メンテナンス性を高めつつ、使いやすさを向上させる・・・といったケースが多いところです。

このため、AppleScriptObjCからAppleScriptを呼び出す場合、メニューやラジオボタンのID(整数)であったり、ファイルのパス(文字列)であったりと、今回の問題にひっかからない利用の仕方がほとんどでした。

OS X 10.10の「通常のAppleScriptの中にAppleScriptObjCを書けるようにする」という拡張により、これまでに少なかった「AppleScriptからAppleScriptObjCの機能を利用し、値を受け取る」という処理パターンが増え、問題点が露見したのでしょう。

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

set aList to {“-1.1″, “1″, “10″, “0.1″, “1.1″, “1.12″, “-1″, “-100″} –Original List
set aRes to sort1DNumList_ascOrder_(aList, true)
–> {”-100″, “-1.1″, “-1″, “0.1″, “1″, “1.1″, “1.12″, “10″}–sort ascending

set bRes to sort1DNumList_ascOrder_(aList, false)
–> {”10″, “1.12″, “1.1″, “1″, “0.1″, “-1″, “-1.1″, “-100″}–sort descending

–Sort 1-Dimension List(String Number List)
on sort1DNumList:theList ascOrder:aBool
  tell current application’s NSSet to set theSet to setWithArray_(theList)
  
tell current application’s NSSortDescriptor to set theDescriptor to sortDescriptorWithKey_ascending_(“floatValue”, aBool)
  
set sortedList to theSet’s sortedArrayUsingDescriptors:{theDescriptor}
  
return (sortedList) as list
end sort1DNumList:ascOrder:

(*
–Sort 1-Dimension List(Number List)
on sort1DNumList2:theList ascOrder:aBool
  set anArray to current application’s NSArray’s arrayWithArray:theList
  set sortedList to anArray’s sortedArrayUsingSelector:(”numberCompare:”)
  return (sortedList) as list
end sort1DNumList2:ascOrder:
*)

–Sort 1-Dimension List(String List)
on sort1DList:theList ascOrder:aBool
  set aDdesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“self” ascending:aBool selector:“localizedCaseInsensitiveCompare:”
  
set theArray to current application’s NSArray’s arrayWithArray:theList
  
return (theArray’s sortedArrayUsingDescriptors:{aDdesc}) as list
end sort1DList:ascOrder:

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

2014/10/28 OS X 10.10のasocで1D Listをソート

OS X 10.10のAppleScript処理系は、マルチコアのCPUを活かして高速に処理を行うように変更されているように見えます。実際、スクリプトエディタ上でAppleScriptを実行すると、

process.png

OS X 10.9までよりもはるかに多くの内部スレッドを生成し、マシンパワーを絞り出すようにチューニングされていることがうかがわれます。

OS X 10.10ではCocoaの機能を直接呼び出すAppleScriptObjCが気軽に使えるようになったため、処理パワーが必要とされるリスト(配列)のソートをAppleScriptObjCと通常のAppleScriptで実行して処理時間を計測してみました。

処理対象の1-Dimension List(1次元配列)は10万アイテムで構成(すべて固定長の乱数を文字列化したもの)。

実行環境は、MacBook Pro Retina 2012(Core i7 2.66GHz x 4 core , 8GB RAM)およびMacBook Air 2011(Core i5 1.6GHz ,4GB RAM)です。

sort_res.png

結果は、ASOCの方が6〜18倍程度高速でした。これは、本機能を使わない手はありません。

ただ、調子に乗って100万アイテムのリストを作らせてみたら・・・RAMを使いつぶしたりスクリプトエディタを無反応にさせたりしたため、10万アイテム程度がこのぐらいのスペックのコンピュータ上でAppleScriptでデータ処理を行わせる場合には適正レベルといえるでしょう。

ぜひ、2D Listのソートもやらせたいですね。

AppleScript名:asocで1D Listをソート(じっけん)
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions

script spd
  property aList : {}
end script

–テスト用データリストの作成(10万アイテム)
set (aList of spd) to {}
repeat with i from 1 to 100000
  set the end of (aList of spd) to (random number from 10000 to 99999) as string
end repeat

–昇順ソートの時間計測
set a1Dat to current date

set aRes to sort1DList_ascOrder_((aList of spd), true)
set item1 to contents of (items -9 thru -1 of aRes)

set b1Dat to current date
set c1Dat to b1Dat - a1Dat

–降順ソートの時間計測
set a2Dat to current date

set aRes to sort1DList_ascOrder_((aList of spd), false)
set item1 to contents of (items -9 thru -1 of aRes)

set b2Dat to current date
set c2Dat to b2Dat - a2Dat

return {c1Dat, c2Dat}
–> {1, 1}

–1D Listをsort / ascOrderがtrueだと昇順ソート、falseだと降順ソート
on sort1DList:theList ascOrder:aBool
  set aDdesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“self” ascending:aBool selector:“localizedCaseInsensitiveCompare:”
  
set theArray to current application’s NSArray’s arrayWithArray:theList
  
return (theArray’s sortedArrayUsingDescriptors:{aDdesc}) as list
end sort1DList:ascOrder:

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

AppleScript名:asで1D Listをソート(じっけん)
use AppleScript version “2.4″
use scripting additions

script spd
  property aList : {}
end script

–テスト用データリストの作成(10万アイテム)
set (aList of spd) to {}
repeat with i from 1 to 100000
  set the end of (aList of spd) to (random number from 10000 to 99999) as string
end repeat

–昇順ソートの時間計測
set a1Dat to current date

set aRes to shellSortAscending(aList of spd)

set b1Dat to current date
set c1Dat to b1Dat - a1Dat

–降順ソートの時間計測
set a2Dat to current date

set aRes to shellSortDescending(aList of spd)

set b2Dat to current date
set c2Dat to b2Dat - a2Dat

return {c1Dat, c2Dat}
–> {19, 8}

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

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

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