Archive for the 'NSMutableSet' Category

2017/12/06 指定フォルダ以下のテキストファイルのファイル名のうち連番部分を抽出して、欠落や重複を検出する

指定フォルダ以下の指定形式のファイルのファイル名をSpotlightですべて取得し、重複や連番部分の欠落を抽出するAppleScriptです。

しょっちゅう同じようなScriptを作っているような気もしますが、それだけ個人的な必要度が高いものかもしれません。

連番部分を含む命名規則を持つファイル群、

  1AXXXXXX-101.txt
  1AXXXXXX-72.txt
  1AXXXXXX-9.txt

から(ファイル名はサンプル)、ファイル名の共通部分をスキャンして、ファイルごとに変更になる部分を抽出しています。一応、ねんのために拡張子を外したデータを処理しています。

ファイル名の共通部分をデータ同士から抽出する、というのが本Scriptの新機軸です。

データすべてから変更部分を取得するのではなく、データの文字数の最大、最小を取得し、それぞれの文字列に該当するデータを10件ずつピックアップし、その間で変更部分の検出を行なっています。

こうした「固定部分の自動検出」というのは、仕様がきちんと存在していない仕事において必要になってくると思われます。すべてのファイル名データから命名ルールを自動検出したり、例外に該当するものを除外したり、ということも考えられそうです。

なお、実行には、Shane StanleyのAppleScriptライブラリ「Metadata Lib」および「BridgePlus」のインストールを必要とします。

AppleScript名:指定フォルダ以下のテキストファイルのファイル名のうち連番部分を抽出して、欠落や重複を検出する
– Created 2017-12-05 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use mdLib : script “Metadata Lib” version “1.0.0″
use bPlus : script “BridgePlus”
–http://piyocast.com/as/archives/5011

property NSCountedSet : a reference to current application’s NSCountedSet
property NSMutableArray : a reference to current application’s NSMutableArray
property SMSForder : a reference to current application’s SMSForder
property NSMutableSet : a reference to current application’s NSMutableSet
property NSIndexSet : a reference to current application’s NSIndexSet

set origFol to (POSIX path of (choose folder))
set txtFiles to mdLib’s searchFolders:{origFol} searchString:“kMDItemContentType == %@” searchArgs:{“public.plain-text”}

–ファイル名部分から拡張子を削除して収集
set anArray to ((current application’s NSMutableArray’s arrayWithArray:txtFiles)’s valueForKeyPath:“lastPathComponent.stringByDeletingPathExtension”)

–ファイル名文字列リストから、共通部分(変更のない部分)を抽出
set intStr to retInterSectionStr(anArray) of me

set cList to {}
repeat with i in (anArray as list)
  set aRes to repChar(i as string, intStr, “”) of me
  
set aNumF to chkNumeric(aRes) of me
  
if aNumF = true then
    set the end of cList to aRes as integer
  else
    log i
  end if
end repeat

set aRes to calcGaps(cList) of me
set bRes to returnDuplicatesOnly(cList) of me

return {gaps:aRes, dups:bRes}
–>  {gaps:{195, 284, 285}, dups:{}}

–与えられた文字列リストのうち、文字列共通部分を抽出
on retInterSectionStr(anArray)
  set aMin to (anArray’s valueForKeyPath:“@min.length”) as integer
  
set aMax to (anArray’s valueForKeyPath:“@max.length”) as integer
  
  
set bList to {}
  
repeat with i from aMin to aMax
    set aCount to 0
    
    
repeat with ii in (anArray as list)
      set aLen to length of ii
      
if aLen = i then
        set the end of bList to contents of ii
        
if aCount = 10 then
          exit repeat
        else
          set aCount to aCount + 1
        end if
      end if
    end repeat
    
  end repeat
  
  
set aStr to contents of first item of bList
  
set bList to rest of bList
  
  
repeat with i in bList
    set bStr to contents of i
    
set intStr to calcIntersection(aStr, bStr) of me
    
if intStr = false then
      exit repeat
    else
      copy intStr to aStr
    end if
  end repeat
  
  
return aStr
end retInterSectionStr

–2つの文字列から共通部分を(先頭から)抽出
on calcIntersection(aStr, bStr)
  set aList to characters of aStr
  
set bList to characters of bStr
  
  
set aLen to length of aList
  
set bLen to length of bList
  
  
if aLen bLen then
    set aMax to bLen
  else
    set aMax to aLen
  end if
  
  
set hitF to false
  
repeat with i from 1 to aMax
    set aChar to contents of item i of aList
    
set bChar to contents of item i of bList
    
if aChar is not equal to bChar then
      set hitF to true
      
exit repeat
    end if
  end repeat
  
  
if hitF = false then return false
  
  
return (text 1 thru (i - 1) of aStr)
end calcIntersection

–文字置換
on repChar(origText as string, targChar as string, repChar as string)
  set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to targChar
  
set tmpList to text items of origText
  
set AppleScript’s text item delimiters to repChar
  
set retText to tmpList as string
  
set AppleScript’s text item delimiters to curDelim
  
return retText
end repChar

–文字列が数字のみから構成されているかを調べて返す
on chkNumeric(checkString as string)
  set digitCharSet to current application’s NSCharacterSet’s characterSetWithCharactersInString:“0123456789″
  
set ret to my chkCompareString:checkString baseString:digitCharSet
  
return ret as boolean
end chkNumeric

on chkCompareString:checkString baseString:baseString
  set aScanner to current application’s NSScanner’s localizedScannerWithString:checkString
  
aScanner’s setCharactersToBeSkipped:(missing value)
  
aScanner’s scanCharactersFromSet:baseString intoString:(missing value)
  
return (aScanner’s isAtEnd()) as boolean
end chkCompareString:baseString:

–連番リストからGap検出
on calcGaps(aList as list)
  set nArray to (NSMutableArray’s arrayWithArray:aList)
  
set maxRes to (nArray’s valueForKeyPath:“@max.self”)’s intValue()
  
set minRes to (nArray’s valueForKeyPath:“@min.self”)’s intValue()
  
  
–最小値から最大値までの連番リスト作成
  
set theIndexSet to NSIndexSet’s indexSetWithIndexesInRange:{minRes, maxRes}
  
set theList to (SMSForder’s arrayWithIndexSet:theIndexSet) as list
  
  
–補集合
  
set aSet to NSMutableSet’s setWithArray:theList
  
set bSet to NSMutableSet’s setWithArray:nArray
  
aSet’s minusSet:bSet
  
  
return aSet’s allObjects() as list
end calcGaps

on returnDuplicatesOnly(aList as list)
  set aSet to NSCountedSet’s alloc()’s initWithArray:aList
  
set bList to (aSet’s allObjects()) as list
  
  
set dupList to {}
  
repeat with i in bList
    set aRes to (aSet’s countForObject:i)
    
if aRes > 1 then
      set the end of dupList to (contents of i)
    end if
  end repeat
  
  
return dupList
end returnDuplicatesOnly

★Click Here to Open This Script 

2017/09/05 指定フォルダ以下のテキストファイルのファイル名冒頭についている数字から欠番を求める

ファイル名の先頭に1〜3桁の数字がついているテキストファイルに対して、指定フォルダ以下のものをすべて取得し、これらの数(連番)に抜けがないかをチェックするAppleScriptです。

filenames2.png

指定フォルダ以下に存在するテキストファイルをSpotlightで検索し、すべてのファイル名を取得してそこから先頭に存在する1〜3桁の数字を取得。全角文字が混入している場合にそなえて数字をすべて全角→半角変換。これらの数(おそらく連番)で途中に抜けがないかチェックします。

実行にはShane StanleyのAppleScript Libraries「BridgePlus」「MetaData Lib」のインストールが必要です。

連番リストの作成や全角→半角変換、Spotlight検索などをScript Librariesの機能に依存して書いています。また、集合(NSMutableSet)を扱えることで非常に高度な処理を完結に記述できています。処理速度も非常に高速です。

 127ファイルの連番チェック処理:0.113457024097 sec.
 449ファイルの連番チェック処理:0.381642997265 sec.

(結果はMacBook Pro Retina 2012 Core i7 2.66GHz+8GBでの所用時間)

AppleScript名:指定フォルダ以下のテキストファイルのファイル名冒頭についている数字から欠番を求める
– Created 2017-09-04 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions
use mdLib : script “Metadata Lib” version “1.0.0″
use bPlus : script “BridgePlus”
–http://piyocast.com/as/archives/4803

property SMSForder : a reference to current application’s SMSForder
property NSMutableArray : a reference to current application’s NSMutableArray
property NSMutableSet : a reference to current application’s NSMutableSet
property NSIndexSet : a reference to current application’s NSIndexSet
property NSRegularExpressionDotMatchesLineSeparators : a reference to current application’s NSRegularExpressionDotMatchesLineSeparators
property NSRegularExpressionAnchorsMatchLines : a reference to current application’s NSRegularExpressionAnchorsMatchLines
property NSRegularExpression : a reference to current application’s NSRegularExpression
property NSString : a reference to current application’s NSString

load framework –BridgePlus’s force framework loading command

–選択したフォルダ以下のPlain TextをすべてSpotlightで求める(POSIX path list)
set theFolder to (choose folder)
set aRes to retMissingNumberFromEachFiles(theFolder, {“public.plain-text”}) of me
–>  {}

on retMissingNumberFromEachFiles(theFolder, fileTypeList)
  set theFiles to mdLib’s searchFolders:{theFolder} searchString:“kMDItemContentType IN[c] %@” searchArgs:fileTypeList
  
if theFiles = {} then return
  
  
–取得したPOSIX Pathのリストからファイル名の部分のみ抽出
  
set anArray to NSMutableArray’s arrayWithArray:theFiles
  
set bArray to (anArray’s valueForKeyPath:“lastPathComponent”) as list
  
  
–各ファイルの名称の冒頭から1〜3桁 の数字を取り出して、全角–>半角変換を行いつつリストに追加
  
set nArray to NSMutableArray’s new()
  
repeat with i in bArray
    set j to contents of i
    
set aRes to (my findPattern:“^\\d{1,3}” inString:j)
    
if aRes is not equal to {} then
      set jj to (contents of first item of aRes) as string
      
set jj2 to (SMSForder’s transformedFrom:jj ICUTransform:“Fullwidth-Halfwidth” inverse:false) as integer
      (
nArray’s addObject:jj2)
    end if
  end repeat
  
  
–最大値、最小値をもとに連番リストを作成し、ファイル名から得られた配列データとの補集合を求める
  
set maxRes to (nArray’s valueForKeyPath:“@max.self”)’s intValue()
  
set minRes to (nArray’s valueForKeyPath:“@min.self”)’s intValue()
  
  
–最小値から最大値までの連番リスト作成
  
set theIndexSet to NSIndexSet’s indexSetWithIndexesInRange:{minRes, maxRes}
  
set theList to (SMSForder’s arrayWithIndexSet:theIndexSet) as list
  
  
set aSet to NSMutableSet’s setWithArray:theList
  
set bSet to NSMutableSet’s setWithArray:nArray
  
aSet’s minusSet:bSet –補集合
  
  
return (aSet’s allObjects() as list)
  
end retMissingNumberFromEachFiles

on findPattern:thePattern inString:theString
  set theOptions to ((NSRegularExpressionDotMatchesLineSeparators) as integer) + ((NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to NSRegularExpression’s regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value)
  
set theFinds to theRegEx’s matchesInString:theString options:0 range:{location:0, |length|:length of theString}
  
set theFinds to theFinds as list
  
set theResult to {}
  
set theNSString to NSString’s stringWithString:theString
  
  
repeat with i in theFinds
    set theRange to (contents of i)’s range()
    
set end of theResult to (theNSString’s substringWithRange:theRange) as string
  end repeat
  
return theResult
end findPattern:inString:

★Click Here to Open This Script 

2017/09/03 ソフトウェア的にアンマウントしたが物理的に接続解除していないDriveの名前を取得

Finder上でいったんマウントしたあと、アンマウントしてもMac本体に物理的に接続したままのドライブ名を取得するAppleScriptです。

  「こんなもの、誰が何のために使うんだろうか?」

と首をひねる内容ですが、自分のところにも以前に1回だけこのような質問が来たことがあります。このScriptのオリジナルはShane Stanleyによるものですが(魔改造してブラッシュアップしていますが)、どういうニーズから作ったのか本人に聞いてみたいものです。

Finder経由でドライブ情報を取得するかぎりではそういう状態にあるドライブの存在を知ることは不可能ですが、diskutil経由で取得できるというのは意外でした。そういう意味では純粋なAppleScriptで書くことも可能ですが、おそらくこの倍ぐらいの長さになるものと思われます。

一応、macOS 10.13 Beta上でテストしてありますが、RAM 4GBのMacBook Airでは何かFinderの動作が不安定でいろいろクラッシュしています。

HFSおよびAPFSのドライブには対応していますが、macOSでマウント可能な他のフォーマットのドライブすべて(HFS、HFS Plus、APFS、WebDAV、UDF、FAT、ExFAT、SMB/CIFS、AFP、NFS、FTP、Xsan、NTFS、CDDAFS、ISO9660)を試せているわけではないので、実用的なレベルに持っていくためにはそのあたりを攻める(実際にテストして拡充させる)必要があることでしょう。

AppleScript名:ソフトウェア的にアンマウントしたが物理的に接続解除していないDriveの名前を取得
– Created 2017-06-20 Shane Stanley
– Modified 2017-06-20 Takaaki Naganoya
use AppleScript version “2.4″ – Yosemite (10.10) or later
use framework “Foundation”
use scripting additions
–http://piyocast.com/as/archives/4802

property NSString : a reference to current application’s NSString
property NSPredicate : a reference to current application’s NSPredicate
property NSMutableSet : a reference to current application’s NSMutableSet
property NSMutableArray : a reference to current application’s NSMutableArray

set dRes to retUnmountedAndUnremovedDriveNames() of me
–> {”Data”}

on retUnmountedAndUnremovedDriveNames()
  set theResult to do shell script “diskutil list -plist”
  
  
– make dictionary from property list
  
set theResult to NSString’s stringWithString:theResult
  
set pListDict to theResult’s propertyList()
  
  
– extract relevant info
  
set disksAndParitions to pListDict’s objectForKey:“AllDisksAndPartitions”
  
  
set partitionsArray to NSMutableArray’s array() – to store values
  
repeat with anEntry in disksAndParitions
    set thePartitions to (anEntry’s objectForKey:“Partitions”)
    
if thePartitions = missing value then – no partitions means a volume
      (partitionsArray’s addObject:anEntry)
    else
      (partitionsArray’s addObjectsFromArray:thePartitions)
    end if
  end repeat
  
  
– filter by Content type
  
set thePred to NSPredicate’s predicateWithFormat:“Content == ’Apple_HFS’ OR Content == ’Apple_APFS’ “
  
set partitionsArray to partitionsArray’s filteredArrayUsingPredicate:thePred
  
  
– get names
  
set dList to (partitionsArray’s valueForKey:“VolumeName”) as list
  
  

  
set edList to retEjectableDriveNames() of me
  
set the end of edList to retBootDriveName() of me
  
set the end of edList to missing value –消し込みのために追加
  
  
set aSet to NSMutableSet’s setWithArray:dList
  
set bSet to NSMutableSet’s setWithArray:edList –Boot drive & local ejectable drives
  
  
aSet’s minusSet:bSet –補集合
  
set dRes to aSet’s allObjects() as list
  
  
return dRes
end retUnmountedAndUnremovedDriveNames

on retEjectableDriveNames()
  tell application “Finder”
    try
      set dList to name of every disk whose ejectable is true
    on error
      set dList to {}
    end try
  end tell
  
return dList
end retEjectableDriveNames

on retBootDriveName()
  tell application “Finder”
    return name of startup disk
  end tell
end retBootDriveName

★Click Here to Open This Script 

2017/05/20 最大公約数を求めるv2

2つの数の最大公約数を求めるAppleScriptです。

素数チェックや素因数分解を作ったので、この手の計算を行ってみました。

AppleScript名:最大公約数を求めるv2
– Created 2017-05-20 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4653

set aRes to getGCDbetweenTwoNums(12, 18) of me
–> 6

–2つの数の最大公約数を求める
on getGCDbetweenTwoNums(aNum, bNum)
  set aRes to getPrimeFactor(aNum) of me
  
set bRes to getPrimeFactor(bNum) of me
  
  
–2つのリストを集合として扱い、積集合を求める
  
set aSet to current application’s NSMutableSet’s setWithArray:aRes
  
set bSet to current application’s NSMutableSet’s setWithArray:bRes
  
aSet’s intersectSet:bSet
  
set cRes to aSet’s allObjects() as list
  
  
set dRes to multipleListElements(cRes) of me
  
return dRes
end getGCDbetweenTwoNums

–与えられた数を素因数分解する
on getPrimeFactor(aTargNum)
  copy aTargNum to a
  
set pFactList to {}
  
  
repeat
    set pRes to checkPrimeNumber(a) of me
    
if pRes = true then
      set the end of pFactList to a
      
return pFactList
    end if
    
    
set aFactor to checkFactor(a) of me
    
set the end of pFactList to aFactor
    
set a to a div aFactor
  end repeat
end getPrimeFactor

–素数チェック
on checkPrimeNumber(aNum)
  repeat with i from 2 to aNum - 1
    if aNum mod i is equal to 0 then
      return false
    end if
  end repeat
  
return true
end checkPrimeNumber

–素因数チェック
on checkFactor(aNum)
  repeat with i from 2 to aNum - 1
    if aNum mod i is equal to 0 then
      return i
    end if
  end repeat
end checkFactor

–リスト内の要素をすべて乗算する
on multipleListElements(aList)
  set aNum to 1
  
set aRes to 1
  
repeat with i in aList
    set aRes to aRes * (aNum * i)
  end repeat
  
return aRes
end multipleListElements

★Click Here to Open This Script 

2017/03/01 NSSet/NSMutableSetの演算 2

NSSet/NSMutableSetの演算について、Shane Stanleyから「こっちのほうが短く書けるぞ」というツッコミが(Thanks!)。

set1.png

AppleScript名:補集合(set difference)v2
– Created 2017-02-24 by Takaaki Naganoya
– Created 2017-03-01 by Shane Stanley
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4491

set aSet to current application’s NSMutableSet’s setWithArray:{1, 2, 3, 4}
set bSet to current application’s NSMutableSet’s setWithArray:{3, 5, 6, 7}

aSet’s minusSet:bSet –補集合
aSet’s allObjects() as list
–>  {2, 1, 4}

★Click Here to Open This Script 

set2.png

AppleScript名:積集合(product)v2
– Created 2017-02-24 by Takaaki Naganoya
– Created 2017-03-01 by Shane Stanley
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4491

set aSet to current application’s NSMutableSet’s setWithArray:{1, 2, 3, 4}
set bSet to current application’s NSMutableSet’s setWithArray:{3, 5, 6, 7}

aSet’s intersectSet:bSet –積集合
aSet’s allObjects() as list
–>  {3}

★Click Here to Open This Script 

set3.png

AppleScript名:和集合(union)v2
– Created 2017-02-24 by Takaaki Naganoya
– Created 2017-03-01 by Shane Stanley
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4491

set aSet to current application’s NSMutableSet’s setWithArray:{1, 2, 3, 4}
set bSet to current application’s NSMutableSet’s setWithArray:{3, 5, 6, 7}

aSet’s unionSet:bSet –和集合
aSet’s allObjects() as list
–>  {5, 1, 6, 2, 7, 3, 4}

★Click Here to Open This Script 

2017/03/01 NSSet/NSMutableSetの演算

NSSet/NSMutableSetが集合を表すClassであることをあとから知り、集合同士の演算の例を調べてみました。こういう地道な調査があとでジワジワ効いてくるのです。

set1.png

AppleScript名:補集合(set difference)
– Created 2017-02-24 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4487

set aArray to current application’s NSArray’s arrayWithArray:{1, 2, 3, 4}
set bArray to current application’s NSArray’s arrayWithArray:{3, 5, 6, 7}

set aSet to current application’s NSMutableSet’s alloc()’s initWithArray:aArray
set bSet to current application’s NSMutableSet’s alloc()’s initWithArray:bArray

aSet’s minusSet:bSet –補集合
aSet’s allObjects() as list
–>  {2, 1, 4}

★Click Here to Open This Script 

set2.png

AppleScript名:積集合(product)
– Created 2017-02-24 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4487

set aArray to current application’s NSArray’s arrayWithArray:{1, 2, 3, 4}
set bArray to current application’s NSArray’s arrayWithArray:{3, 5, 6, 7}

set aSet to current application’s NSMutableSet’s alloc()’s initWithArray:aArray
set bSet to current application’s NSMutableSet’s alloc()’s initWithArray:bArray

aSet’s intersectSet:bSet –積集合
aSet’s allObjects() as list
–>  {3}

★Click Here to Open This Script 

set3.png

AppleScript名:和集合(union)
– Created 2017-02-24 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4487

set aArray to current application’s NSArray’s arrayWithArray:{1, 2, 3, 4}
set bArray to current application’s NSArray’s arrayWithArray:{3, 5, 6, 7}

set aSet to current application’s NSMutableSet’s alloc()’s initWithArray:aArray
set bSet to current application’s NSMutableSet’s alloc()’s initWithArray:bArray

aSet’s unionSet:bSet –和集合
aSet’s allObjects() as list
–>  {5, 1, 6, 2, 7, 3, 4}

★Click Here to Open This Script 

2016/01/10 レコードのリストからラベル値のみ抽出してユニーク化してソート v2

Cocoaの機能を用いて、レコードのリストからラベル値のみ抽出して、ユニーク化してソートして返すAppleScriptです。

Shane Stanleyから指摘があって、「そこもっと短くかけるよ〜」ということで、大幅に短くなりました(Thanks !!)。NSMutableSetを使うのは、とってもいい方法だと思います。

AppleScript名:レコードのリストからラベル値のみ抽出してユニーク化してソート v2
– Created 2016-01-08 by Takaaki Naganoya
– Modified 2016-01-08 by Shane Stanley (retEveryKeys)
– 2016 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

set aDic to {{field1:"test 10", field2:"test 20"}, {field1:"test 11", field2:"test 21"}, {field1:"test 12", field2:"test 22"}, {field1:"test 13", field2:"test 23"}, {field1:"test 14", field2:"test 24"}, {field1:"test 15", field2:"test 25"}, {field1:"test 16", field2:"test 26"}, {field1:"test 17", field2:"test 27"}, {field1:"test 18", field2:"test 28"}, {field1:"test 19", field2:"test 29"}, {field1:"test 10", field2:"test 20"}, {field1:"test 11", field2:"test 21"}, {field1:"test 12", field2:"test 22"}, {field1:"test 13", field2:"test 23"}, {field1:"test 14", field2:"test 24"}, {field1:"test 15", field2:"test 25"}, {field1:"test 16", field2:"test 26"}, {field1:"test 17", field2:"test 27"}, {field1:"test 18", field2:"test 28"}, {field1:"test 19", field3:"test 29"}}

set theDataSource to current application’s NSMutableArray’s arrayWithArray:aDic
set kList to retEveryKeys(theDataSource) of me
–>  {"field1", "field2", "field3"}

–リストになったRecordのすべてのアイテムのkey値を取得してユニーク化してソートして返す
on retEveryKeys(aDic)
  set tempSet to current application’s NSMutableSet’s |set|()
  
repeat with aDict in aDic
    (tempSet’s addObjectsFromArray:(aDict’s allKeys()))
  end repeat
  
return (tempSet’s allObjects()’s sortedArrayUsingSelector:"compare:") as list
end retEveryKeys

★Click Here to Open This Script 

2015/09/11 与えられたテキストの言語を自動判別して対応する言語のTTS Voiceで読み上げ

Cocoaの機能を用いて、与えられたテキストの言語を自動判別して対応する言語のTTS Voiceで読み上げするAppleScriptです。Shane StanleyのAppleScript Libraryである「BridgePlus」(フリー)のインストールが必要です。

こういうのがやりたくて、地道に部品を揃えてきたわけで・・・やらないわけにはいきません。

ただし、思ったよりも泥沼に足を突っ込みかけました。食後のハイキングのはずが、雪山で遭難しかけたという印象です。

NSSpeechSynthesizerのavailableVoices()は「インストールされていないTTS Voice」も返してくるため、せっかくテキストの言語を特定して、TTS Voiceを取得しても・・・インストールされていないTTS Voiceが返ってくるとお手上げです(T_T)。

tts_voice_list.png

英語だとNSLinguisticTaggerによる判定で”en”が、フランス語だと”fr”が返ってきますが、”en”はあくまで英語を示すものであり、”en_GB”もあれば”en_US”もあれば、”en_IN”(インド)、”en-scotland”(スコットランド)、”en_ZA”(南アフリカ)もあるわけです(自分も物好きですが、南アフリカのTTS Voiceはインストールしていません)。

“en”をキーにして、全TTS Voiceから言語コードを取得し、最初にヒットした言語コードが”en_ZA”であれば南アフリカのTTS Voiceでsayコマンドの実行を行おうとしてしまうので、”en”を”en-US”に置き換えるような、つじつまあわせの処理が必要になってしまいました(もう少しうまいやり方もありそうですが、食後の散歩のレベルを超えてしまいますので)。

実際に実戦レベルのAppleScriptを書いてみるまで、見えてこない「落とし穴」もあるので、こうして細かいScriptを書いておくという作業はなかなかあなどれません、、、

AppleScript名:与えられたテキストの言語を自動判別して対応する言語のTTS Voiceで読み上げ
– Created 2015-09-11 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use BridgePlus : script “BridgePlus”

load framework

set aRes to getVoiceLanguageFromTTSVoices() –Language Code

–English, French, Japanese, Chinese
set cList to {“With the new MacBook, we set out to do the impossible.”, “Le nouveau MacBook est le résultat d’un défi presque impossible”, “新しいMacBookとともに、私たちは不可能だと思えることに挑みました。”, 于全新 MacBook,我们给自己定了一个几乎不可能实现的目}

–各言語テキストでループ
repeat with i in cList
  set j to contents of i
  
set aLang to specifyLanguageOfText(j) –テキストから言語コードを取得
  
  
–つじつまあわせ。ちょっとカッコ悪い
  
–(NSSpeechSynthesizerのavailableVoicesでインストールされていないVoiceまで返ってくることへの対策)
  
if aLang = “en” then
    set aLang to “en-US”
  else if aLang = “fr” then
    set aLang to “fr-FR”
  else if aLang = “ja” then
    set aLang to “ja-JP”
  end if
  
  
–全ボイス中から言語をサポートしているものを抽出
  
repeat with ii in aRes
    set jj to contents of ii
    
    
if (jj as text) is equal to (aLang as text) then
      
      
–最初にヒットした言語コードでPremium Voiceを検索
      
set v1fList to getPremiumVoiceNameWithFiltering(“VoiceGenderFemale”, jj)
      
set v1mList to getPremiumVoiceNameWithFiltering(“VoiceGenderMale”, jj)
      
set v1List to v1fList & v1mList
      
      
if v1List = {} then
        –Premium Voiceでヒットしなかった場合に、すべてのVoiceを検索
        
set v2fList to getAllVoiceNameWithFiltering(“VoiceGenderFemale”, jj)
        
set v2mList to getAllVoiceNameWithFiltering(“VoiceGenderMale”, jj)
        
set v2List to v2fList & v2mList
        
        
if v2List = {} then
          display dialog “この言語(” & jj & “)をサポートするTTS Voiceはインストールされていません。” –Error
          
exit repeat
        else
          copy v2List to v1List
        end if
        
      end if
      
      
repeat with iii in v1List
        set aVoice to contents of iii
        
try
          –総当たりでSayコマンド実行(インストールされていないTTS Voiceも取得できてしまうため)
          
tell current application
            say j using aVoice
          end tell
          
exit repeat
        end try
      end repeat
      
      
exit repeat
    end if
  end repeat
  
end repeat

–Premium Voices Only
on getPremiumVoiceNameWithFiltering(voiceGender, voiceLang)
  –Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDIc)
  end repeat
  
  
–Filter Voice
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat_(“VoiceLanguage == %@ and VoiceGender == %@ and VoiceIdentifier ENDSWITH[c] %@”, voiceLang, voiceGender, “premium”)
  
set filteredArray to outArray’s filteredArrayUsingPredicate:aPredicate
  
set aReList to (filteredArray’s valueForKey:“VoiceName”) as list
  
  
return aReList
  
end getPremiumVoiceNameWithFiltering

–All Voices (VoiceLanguageで抽出)
on getAllVoiceNameWithFiltering(voiceGender, voiceLang)
  –Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDIc)
  end repeat
  
  
–Filter Voice
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat_(“VoiceLanguage == %@ and VoiceGender == %@”, voiceLang, voiceGender)
  
  
set filteredArray to outArray’s filteredArrayUsingPredicate:aPredicate
  
set aReList to (filteredArray’s valueForKey:“VoiceName”) as list
  
  
return aReList
  
end getAllVoiceNameWithFiltering

on getLocaleIdentifierFromTTSVoices()
  –Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDIc)
  end repeat
  
  
set aResArray to (outArray’s valueForKey:“VoiceLocaleIdentifier”)
  
  
set aSet to current application’s NSMutableSet’s setWithArray:aResArray
  
set aResList to aSet’s allObjects()
  
set bResList to BridgePlus’s listByDeletingBlanksIn:aResList –Remove Blank Items
  
  
return bResList
end getLocaleIdentifierFromTTSVoices

on getVoiceLanguageFromTTSVoices()
  –Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDIc)
  end repeat
  
  
set aResArray to (outArray’s valueForKey:“VoiceLanguage”)
  
set aSet to current application’s NSMutableSet’s setWithArray:aResArray
  
set aResList to aSet’s allObjects()
  
set bResList to BridgePlus’s listByDeletingBlanksIn:aResList –Remove Blank Items
  
  
return bResList
end getVoiceLanguageFromTTSVoices

on specifyLanguageOfText(aStr)
  set aNSstring to current application’s NSString’s stringWithString:aStr
  
set tagSchemes to current application’s NSArray’s arrayWithObjects:(current application’s NSLinguisticTagSchemeLanguage)
  
set tagger to current application’s NSLinguisticTagger’s alloc()’s initWithTagSchemes:tagSchemes options:0
  
tagger’s setString:aNSstring
  
set aLanguage to tagger’s tagAtIndex:0 |scheme|:(current application’s NSLinguisticTagSchemeLanguage) tokenRange:(missing value) sentenceRange:(missing value)
  
return aLanguage as text
end specifyLanguageOfText

–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 (ASify from cArray) as list
  
return bList
end uniquifyAndSort1DList

–文字置換
on repChar(aStr, targStr, repStr)
  set aString to current application’s NSString’s stringWithString:aStr
  
set bString to aString’s stringByReplacingOccurrencesOfString:targStr withString:repStr
  
set cString to (ASify from bString) as string
  
return cString
end repChar

★Click Here to Open This Script 

2015/08/26 ASOCで性別と言語コードを指定してTTS voiceを取得 v2

Cocoaの機能を用いて、Text To Speechの音声から性別と言語を指定して、TTS Voice名称をリストで返すAppleScriptです。最初のバージョンに対して、「Premium」voiceに限定しないvoice名取得ルーチン「getAllVoiceNameWithFiltering」を追加しています。ほかに、機能的な差異はありません。

前バージョンに対し、Shane StanleyからNSPredicateの指定について、

set aStr to current application’s NSString’s stringWithString:(”VoiceLocaleIdentifier == ‘” & voiceLang & “‘ and  VoiceGender == ‘” & voiceGender & “‘ and VoiceIdentifier ENDSWITH[c] ‘premium’”)
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aStr

って、書いてあるけど引数をNSPredicateの指定文字列から分離したほうがクォートで囲む囲まないで悩まなくていいから、分離しといたほうがいいよ(意訳)という指摘があって・・・実際、NSPredicate以外のコードはすぐに書けたものの・・・クォートの指定でけっこう試行錯誤して悩んでいたので、まったくその通り(^ー^;;;;

set aPredicate to current application’s NSPredicate’s predicateWithFormat_(”VoiceLocaleIdentifier == %@ and VoiceGender == %@ and VoiceIdentifier ENDSWITH[c] %@”, voiceLang, voiceGender, “premium”)

とか(ここで「predicateWithFormat_」とアンダースコアで書かないと構文確認をパスできないところにASOCの闇と、Shaneの試行錯誤を感じます、、、)、

set aPredicate to current application’s NSPredicate’s predicateWithFormat:”VoiceLocaleIdentifier == %@ and VoiceGender == %@ and VoiceIdentifier ENDSWITH[c] %@” argumentArray:{voiceLang, voiceGender, “premium”}

みたいに書いたほうがシンプルでいいよ的な話で、まさにそのとおり。で、いろいろ書き換えておきました。

各TTS Voiceについては、性別、言語、国などのほかに「年齢」というパラメータも持っているのですが、年齢を条件にして綿密に絞り込む・・・というほどには、TTS Voiceの数が多くないので、年齢は考慮していません。

これ(↑)を掲載するまでに、「クリップボードの内容をスタイル付きテキストで取得する方法」について調べ、調べきれなかったので、do shell script使いまくりの昔作ったAppleScriptでHTMLに書き出した、というオチもありました。Cocoaのクリップボード関連、普段使わないうえに、いざ真剣に調べだしてもあんまりまとまったまともでわかりやすい情報がなくて困りものです。

AppleScript名:ASOCで性別と言語コードを指定してTTS voiceを取得 v2
– Created 2015-08-25 by Takaaki Naganoya
– Modified 2015-08-26 by Shane Stanley, Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use BridgePlus : script “BridgePlus”

load framework

set v1Res to getPremiumVoiceNameWithFiltering(“VoiceGenderMale”, “ja_JP”)
–>  {​​​​​”Otoya”​​​}

set v2Res to getPremiumVoiceNameWithFiltering(“VoiceGenderFemale”, “en_US”)
–>  {​​​​​”Allison”, ​​​​​”Ava”, ​​​​​”Jill”, ​​​​​”Samantha”​​​}

set v3Res to getAllVoiceNameWithFiltering(“VoiceGenderFemale”, “en_US”)
–>  {​​​​​”Agnes”, ​​​​​”Allison”, ​​​​​”Ava”, ​​​​​”Jill”, ​​​​​”Kathy”, ​​​​​”Princess”, ​​​​​”Samantha”, ​​​​​”Vicki”, ​​​​​”Victoria”​​​}

–Premium Voices Only
on getPremiumVoiceNameWithFiltering(voiceGender, voiceLang)
  –Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDIc)
  end repeat
  
  
–Filter Voice
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat_(“VoiceLocaleIdentifier == %@ and VoiceGender == %@ and VoiceIdentifier ENDSWITH[c] %@”, voiceLang, voiceGender, “premium”)
  
set filteredArray to outArray’s filteredArrayUsingPredicate:aPredicate
  
set aReList to (filteredArray’s valueForKey:“VoiceName”) as list
  
  
return aReList
  
end getPremiumVoiceNameWithFiltering

–All Voices
on getAllVoiceNameWithFiltering(voiceGender, voiceLang)
  –Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDIc)
  end repeat
  
  
–Filter Voice
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat_(“VoiceLocaleIdentifier == %@ and VoiceGender == %@”, voiceLang, voiceGender)
  
  
set filteredArray to outArray’s filteredArrayUsingPredicate:aPredicate
  
set aReList to (filteredArray’s valueForKey:“VoiceName”) as list
  
  
return aReList
  
end getAllVoiceNameWithFiltering

on getLaunguageCodeFromTTSVoices()
  
  
–Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDict to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDict)
  end repeat
  
  
set aResArray to (outArray’s valueForKey:“VoiceLocaleIdentifier”)
  
  
set aSet to current application’s NSMutableSet’s setWithArray:aResArray
  
set aResList to aSet’s allObjects()
  
  
set bResList to BridgePlus’s listByDeletingBlanksIn:aResList –Remove Blank Items
  
  
return bResList
  
end getLaunguageCodeFromTTSVoices

★Click Here to Open This Script 

2015/08/25 ASOCで性別と言語コードを指定して高音質voiceを取得

Cocoaの機能を用いて、Text To Speechの音声から性別と言語を指定して、高音質のVoice名称をリストで返すAppleScriptです。

voice1.png
▲システム環境設定「音声入力と読み上げ」>「テキスト読み上げ」

voice2.png
▲OSにインストールされているVoiceリスト

男性:VoiceGenderMale
女性:VoiceGenderFemale

で性別を指定して高音質のvoice名称を取得します。

アメリカ英語(en_US)で男性の音声をどれでもいいから指定したいが、どの音声が入っているかは特定できないしデフォルトで指定されているかどうかもわからない、といった場合にある程度の見当をつけるために作ったものです。

TTS Voiceで使用可能な言語コード一覧については、「ASOCで全voiceから言語コードを抽出」のプログラムで取得できるようにしておきました。

AppleScript名:ASOCで性別と言語コードを指定して高音質voiceを取得
– Created 2015-08-25 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”

set v1Res to getPremiumVoiceNameWithFiltering(“VoiceGenderMale”, “ja_JP”)
–>  {​​​​​”Otoya”​​​}

set v2Res to getPremiumVoiceNameWithFiltering(“VoiceGenderFemale”, “en_US”)
–>  {​​​​​”Allison”, ​​​​​”Ava”, ​​​​​”Jill”, ​​​​​”Samantha”​​​}

set v3Res to getPremiumVoiceNameWithFiltering(“VoiceGenderMale”, “en_GB”)
–> {”Daniel”}

–Premium Voices Only
on getPremiumVoiceNameWithFiltering(voiceGender, voiceLang)
  –Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDIc)
  end repeat
  
  
–Filter Voice
  
set aStr to current application’s NSString’s stringWithString:(“VoiceLocaleIdentifier == ’” & voiceLang & “’ and VoiceGender == ’” & voiceGender & “’ and VoiceIdentifier ENDSWITH[c] ’premium’”)
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aStr
  
set filteredArray to outArray’s filteredArrayUsingPredicate:aPredicate
  
set aReList to (filteredArray’s valueForKey:“VoiceNameRoot”) as list
  
  
return aReList
  
end getPremiumVoiceNameWithFiltering

★Click Here to Open This Script 

AppleScript名:ASOCで全voiceから言語コードを抽出
– Created 2015-08-25 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use BridgePlus : script “BridgePlus”

load framework

set cRes to getLaunguageCodeFromTTSVoices()
–>  {​​​​​”fr_FR”, ​​​​​”zh_TW”, ​​​​​”it_IT”, ​​​​​”en_ZA”, ​​​​​”es_AR”, ​​​​​”ko_KR”, ​​​​​”ro_RO”, ​​​​​”en_IN”, ​​​​​”fr_CA”, ​​​​​”hi_IN”, ​​​​​”da_DK”, ​​​​​”en-scotland”, ​​​​​”pt_BR”, ​​​​​”zh_CN”, ​​​​​”sv_SE”, ​​​​​”es_ES”, ​​​​​”hu_HU”, ​​​​​”ar_SA”, ​​​​​”en_GB”, ​​​​​”ja_JP”, ​​​​​”fi_FI”, ​​​​​”zh_HK”, ​​​​​”tr_TR”, ​​​​​”nb_NO”, ​​​​​”pl_PL”, ​​​​​”id_ID”, ​​​​​”cs_CZ”, ​​​​​”el_GR”, ​​​​​”he_IL”, ​​​​​”ru_RU”, ​​​​​”de_DE”, ​​​​​”en_AU”, ​​​​​”nl_BE”, ​​​​​”pt_PT”, ​​​​​”th_TH”, ​​​​​”sk_SK”, ​​​​​”en_US”, ​​​​​”en_IE”, ​​​​​”nl_NL”, ​​​​​”es_MX”​​​}

on getLaunguageCodeFromTTSVoices()
  
  
–Make Blank Array
  
set outArray to current application’s NSMutableArray’s arrayWithObject:{}
  
  
–Make Installed Voice List
  
set aList to current application’s NSSpeechSynthesizer’s availableVoices()
  
set bList to aList as list
  
  
repeat with i in bList
    set j to contents of i
    
set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j)
    (
outArray’s addObject:aDIc)
  end repeat
  
  
set aResArray to (outArray’s valueForKey:“VoiceLocaleIdentifier”)
  
  
set aSet to current application’s NSMutableSet’s setWithArray:aResArray
  
set aResList to aSet’s allObjects()
  
  
set bResList to BridgePlus’s listByDeletingBlanksIn:aResList –Remove Blank Items
  
  
return bResList
  
end getLaunguageCodeFromTTSVoices

★Click Here to Open This 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 

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/11/22 リストから抽出(asoc)

リストから条件に合う項目だけを抽出するAppleScriptです。

こういう感じで1D Listだけでなく2D Listを抽出できると便利でしょう。ああ、2D Listの抽出をやりたい、、、

AppleScript名:listの項目をフィルタリング(項目文字長)
– 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 aList to {“piyomaru”, “Piyomaru Software”, “Naganoya”, “Takaaki”, “MacBook Pro Retina mid 2012″}

set bList to filterListUsingPredicate(aList, “length > 8″) –文字列長が8文字より長い項目を返す
–> {”MacBook Pro Retina mid 2012″, “Piyomaru Software”}

set cList to filterListUsingPredicate(aList, “SELF MATCHES ’.*e$’”) –正規表現で末尾が”e”
–> {”Piyomaru Software”}

set dList to filterListUsingPredicate(aList, “SELF LIKE ’piyo*’”)
–> {”piyomaru”}

set eList to filterListUsingPredicate(aList, “SELF LIKE[c] ’piyo*’”)
–> {”piyomaru”, “Piyomaru Software”}

set fList to filterListUsingPredicate(aList, “SELF CONTAINS[c] ’Piyo’”)
–> {”piyomaru”, “Piyomaru Software”}

on filterListUsingPredicate(aList as list, aPredicateStr as string)
  –ListからNSArrayへの型変換
  
set setKey to current application’s NSMutableSet’s setWithArray:aList
  
  
–抽出
  
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
  
return cRes
end filterListUsingPredicate

★Click Here to Open This Script 

2014/11/18 listの共通項を返す(asoc)

2つのListをくらべて、共通項を返すAppleScriptです。

Cocoaの流儀にしたがって、Cocoaの機能を使って実装してみました。

AppleScript名:listの共通項を返す
– Created 2014-11-18 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 bList to {0, 1, 2, 4, 5, 6}

set aRes to getSameItemsInLists(aList, bList) of me
–> {2, 5, 1, 4}

on getSameItemsInLists(aList as list, bList as list)
  
  
–ASオブジェクトをCocoaオブジェクトに変換
  
set aArray to current application’s NSArray’s arrayWithArray:aList
  
set bArray to current application’s NSArray’s arrayWithArray:bList
  
  
– まとめる
  
set allSet to current application’s NSMutableSet’s setWithArray:aArray
  
allSet’s addObjectsFromArray:bArray
  
  
–重複する要素のみ抜き出す
  
set duplicateSet to current application’s NSMutableSet’s setWithArray:aArray
  
duplicateSet’s intersectSet:(current application’s NSSet’s setWithArray:bArray)
  
  
–重複部分だけを返す
  
set resArray to duplicateSet’s allObjects()
  
  
set resList to resArray’s ASify() as list
  
  
return resList
  
end getSameItemsInLists

★Click Here to Open This Script 

2014/11/18 list同士のdiffをとる(asoc)

2つのListのdiffをとるAppleScriptです。

検索していろいろ調べ、主にこちらのページの内容を参考にして、Cocoaの流儀にしたがって、Cocoaの機能を使って実装してみました。

以前にAppleScriptだけで実装したものもありますが、データ量が増えるとこちらの方が有利と思われます。

・・・などと書いていたら、Shane Stanleyが2つのAppleScriptの実行速度を計測するツール「Script Geek v.1.27」を公開していました

geek.png

この用途のために作られたとしか思えないツールなので、素直に使っておきましょう。指定回数実行して、平均実行時間を計測してくれるツールです。

実際に使ってみると、

(1)テストデータ作成部分を時間計測してほしくない
(2)計測結果をグラフ表示してほしい
(3)テスト中に画面の内容をアップデートしたほうがいい

といった率直な感想が(^ー^;;

それはいいとして、ASだけで実装したものとASOCで実装したものだとASOCの方が120倍速いという結果が出ました。ASだけで実装した方は、けっこう忙しいときに書いたらしく処理内容が入り組んでおり「どうしてこう書いたんだろう」という話が思い出せません、、、

また、繰り返し実行するとASOCの処理速度が(一桁ぐらい)上がるのに驚きました(初回実行時は0.27秒なのに、平均実行速度が0.03秒、、、)。

(4)各計測結果を数値またはグラフで表示

があると、どういった傾向があるのか分かってよいでしょう。

AppleScript名:listのdiffをとる
– Created 2014-11-18 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 bList to {0, 1, 2, 4, 5, 6}

set aRes to diffLists(aList, bList) of me
–> {0, 6, 3}

on diffLists(aList as list, bList as list)
  –以下を参照
  
–http://qiita.com/ar_tama/items/b69d6ab5c505be89c414
  
  
–ASオブジェクトをCocoaオブジェクトに変換
  
set aArray to current application’s NSArray’s arrayWithArray:aList
  
set bArray to current application’s NSArray’s arrayWithArray:bList
  
  
– まとめる
  
set allSet to current application’s NSMutableSet’s setWithArray:aArray
  
allSet’s addObjectsFromArray:bArray
  
  
–重複する要素のみ抜き出す
  
set duplicateSet to current application’s NSMutableSet’s setWithArray:aArray
  
duplicateSet’s intersectSet:(current application’s NSSet’s setWithArray:bArray)
  
  
–重複部分を削除する
  
allSet’s minusSet:duplicateSet
  
set resArray to allSet’s allObjects()
  
  
set resList to resArray’s ASify() as list
  
  
return resList
  
end diffLists

★Click Here to Open This Script 

AppleScript名:listのdiffをとる(時間計測用)
– Created 2014-11-18 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

script spd
  property aList : {}
  
property bList : {}
end script

set aList of spd to {}
set bList of spd to {}

repeat 1000 times
  set the end of aList of spd to random number from 1 to 1000
  
set the end of bList of spd to random number from 1 to 1000
end repeat

set aRes to diffLists(aList of spd, bList of spd) of me

on diffLists(aList as list, bList as list)
  –以下を参照
  
–http://qiita.com/ar_tama/items/b69d6ab5c505be89c414
  
  
–ASオブジェクトをCocoaオブジェクトに変換
  
set aArray to current application’s NSArray’s arrayWithArray:aList
  
set bArray to current application’s NSArray’s arrayWithArray:bList
  
  
– まとめる
  
set allSet to current application’s NSMutableSet’s setWithArray:aArray
  
allSet’s addObjectsFromArray:bArray
  
  
–重複する要素のみ抜き出す
  
set duplicateSet to current application’s NSMutableSet’s setWithArray:aArray
  
duplicateSet’s intersectSet:(current application’s NSSet’s setWithArray:bArray)
  
  
–重複部分を削除する
  
allSet’s minusSet:duplicateSet
  
set resArray to allSet’s allObjects()
  
  
set resList to resArray’s ASify() as list
  
  
return resList
  
end diffLists

★Click Here to Open This Script 

AppleScript名:listのdiffをASだけでとる(時間計測用)

script spd
  property aList : {}
  
property bList : {}
end script

set aList of spd to {}
set bList of spd to {}

repeat 1000 times
  set the end of aList of spd to random number from 1 to 1000
  
set the end of bList of spd to random number from 1 to 1000
end repeat

set aRes to diffList(aList of spd, bList of spd) of listDiffKit
–> {{0, 9, 11}, {5}}

script listDiffKit
  
  
–リスト同士のdiffをとる
  
on diffList(aaList, bbList)
    copy {aaList, bbList} to {aList, bList} –fix?
    
set notExistInBlist to {}
    
    
if aList is equal to bList then
      return {{}, {}}
    end if
    
    
repeat with i in aList
      set j to contents of i
      
if j is in bList or j is equal to bList then
        set bList to deleteSpecifiedItemFromList(bList, j) of me
      else
        set the end of notExistInBlist to j
      end if
    end repeat
    
    
return {notExistInBlist, bList}
  end diffList
  
  
–指定内容の要素をリストから削除して返す
  
on deleteSpecifiedItemFromList(aList, anItem)
    set iCount to 1
    
repeat with i in aList
      set j to contents of i
      
if j = anItem then
        set aaList to oneItemDelete(aList, iCount) of me
        
return aaList
      end if
      
set iCount to iCount + 1
    end repeat
    
    
return aList
    
  end deleteSpecifiedItemFromList
  
  
–リスト中の指定要素を削除して返す
  
on oneItemDelete(aList, chgNum)
    set newList to {}
    
set aLen to length of aList
    
    
if chgNum = 1 then
      set aLen to length of aList
      
if aLen > 1 then
        set maeList to items 2 thru aLen of aList
        
set newList to maeList
      else
        set newList to {}
      end if
    else if chgNum = aLen then
      set maeList to items 1 thru (aLen - 1) of aList
      
set newList to maeList
      
    else
      set maeList to items 1 thru (chgNum - 1) of aList
      
set atoList to items (chgNum + 1) thru -1 of aList
      
set newList to maeList & atoList
    end if
    
    
return newList
  end oneItemDelete
  
  
on retDelimedText(aList, aDelim)
    set aText to “”
    
set curDelim to AppleScript’s text item delimiters
    
set AppleScript’s text item delimiters to aDelim
    
set aText to aList as text
    
set AppleScript’s text item delimiters to curDelim
    
return aText
  end retDelimedText
  
  
–文字置換ルーチン
  
on repChar(origText, targStr, repStr)
    set {txdl, AppleScript’s text item delimiters} to {AppleScript’s text item delimiters, targStr}
    
set temp to text items of origText
    
set AppleScript’s text item delimiters to repStr
    
set res to temp as text
    
set AppleScript’s text item delimiters to txdl
    
return res
  end repChar
end script

★Click Here to Open This Script