Archive for 8月, 2017

2017/08/31 指定フォルダ以下のすべてのファイルを再帰で取得 v2

指定フォルダ以下のすべてのファイルを再帰で取得するAppleScriptです。Finder経由ではなくNSFileManager経由で高速に処理します。

さすがにSpotlightの方が(実測値で70倍ぐらい)高速なので、Spotlightが効かない場所に対する非常用という位置づけです。

掲載リストのようにデスクトップフォルダ以下の、拡張子「.scpt」「.scptd」の書類のパスをPOSIX pathのlistで取得する処理を自分の環境で行なってみたところ、

  NSFileManager経由での再帰取得処理:1.5 sec.
  Spotlight経由でのファイル取得処理:0.02 sec.

という結果になりました(5回実行時の平均値)。いまどきFinderで再帰処理とかいうのは選択肢として「絶対にありえない」ので比較もしていませんが、同じ環境で同じ処理を行うとおそらく「数分」から「十数分」といったオーダーになるものと思います。

日常的にSpotlightによるファイル取得を行なっているので、本処理が活躍するケースはほとんどない見込みですが、ねんのため(Spotlightが効かない&メタデータが破損しているダメ環境でファイルを取得するための臨時処理)。

一緒に掲載しているSpotlight検索Scriptでは、Shane StanleyのMetadata Libを呼び出しています。実行のためには、Metadata Libを~/Libraries/Script Librariesフォルダにインストールしておく必要があります。

AppleScript名:指定フォルダ以下のすべてのファイルを再帰で取得 v2
– Created 2017-08-04 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4796

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

–set aFol to POSIX path of (choose folder)
set aFol to POSIX path of (path to desktop folder)

–set aList to retFullPathWithinAFolderWithRecursive(aFol) of me
–set bList to retFullPathWithinAFolderWithRecursiveFilterByExt(aFol, “scpt”) of me
set cList to retFullPathWithinAFolderWithRecursiveFilterByExtList(aFol, {“scpt”, “scptd”}) of me
–set dList to retFullPathWithinAFolderWithRecursiveFilterByExtAndFileNameString(aFol, “png”, “スクリーン”) of me
–set eList to retFullPathWithinAFolderWithRecursiveFilterByExtAndFileNameString(aFol, {”scpt”, “scptd”}, “並列”) of me

–指定フォルダ以下のすべてのファイルを再帰で取得
on retFullPathWithinAFolderWithRecursive(aFol)
  set anArray to NSMutableArray’s array()
  
set aPath to NSString’s stringWithString:aFol
  
set dirEnum to NSFileManager’s defaultManager()’s enumeratorAtPath:aPath
  
  
repeat
    set aName to (dirEnum’s nextObject())
    
if aName = missing value then exit repeat
    
set aFullPath to aPath’s stringByAppendingPathComponent:aName
    
anArray’s addObject:aFullPath
  end repeat
  
  
return anArray as list
end retFullPathWithinAFolderWithRecursive

–指定フォルダ以下のすべてのファイルを再帰で取得(拡張子で絞り込み)
on retFullPathWithinAFolderWithRecursiveFilterByExt(aFol, aExt)
  set anArray to NSMutableArray’s array()
  
set aPath to NSString’s stringWithString:aFol
  
set dirEnum to NSFileManager’s defaultManager()’s enumeratorAtPath:aPath
  
  
repeat
    set aName to (dirEnum’s nextObject())
    
if aName = missing value then exit repeat
    
set aFullPath to aPath’s stringByAppendingPathComponent:aName
    
anArray’s addObject:aFullPath
  end repeat
  
  
set thePred to NSPredicate’s predicateWithFormat:“pathExtension == [c]%@” argumentArray:{aExt}
  
set bArray to anArray’s filteredArrayUsingPredicate:thePred
  
  
return bArray as list
end retFullPathWithinAFolderWithRecursiveFilterByExt

–指定フォルダ以下のすべてのファイルを再帰で取得(拡張子リストで絞り込み)
on retFullPathWithinAFolderWithRecursiveFilterByExtList(aFol, aExtList)
  set anArray to NSMutableArray’s array()
  
set aPath to NSString’s stringWithString:aFol
  
set dirEnum to NSFileManager’s defaultManager()’s enumeratorAtPath:aPath
  
  
repeat
    set aName to (dirEnum’s nextObject())
    
if aName = missing value then exit repeat
    
set aFullPath to aPath’s stringByAppendingPathComponent:aName
    
anArray’s addObject:aFullPath
  end repeat
  
  
set thePred to NSPredicate’s predicateWithFormat:“pathExtension IN [c]%@” argumentArray:{aExtList}
  
set bArray to anArray’s filteredArrayUsingPredicate:thePred
  
  
return bArray as list
end retFullPathWithinAFolderWithRecursiveFilterByExtList

–指定フォルダ以下のすべてのファイルを再帰で取得(文字列と拡張子で絞り込み)
on retFullPathWithinAFolderWithRecursiveFilterByExtListAndFileNameString(aFol, aExt, aNameString)
  set anArray to NSMutableArray’s array()
  
set aPath to NSString’s stringWithString:aFol
  
set dirEnum to NSFileManager’s defaultManager()’s enumeratorAtPath:aPath
  
  
repeat
    set aName to (dirEnum’s nextObject())
    
if aName = missing value then exit repeat
    
set aFullPath to aPath’s stringByAppendingPathComponent:aName
    
anArray’s addObject:aFullPath
  end repeat
  
  
set thePred to NSPredicate’s predicateWithFormat:“pathExtension == [c]%@ && lastPathComponent CONTAINS %@” argumentArray:{aExt, aNameString}
  
set bArray to anArray’s filteredArrayUsingPredicate:thePred
  
  
return bArray as list
end retFullPathWithinAFolderWithRecursiveFilterByExtListAndFileNameString

–指定フォルダ以下のすべてのファイルを再帰で取得(文字列と拡張子リストで絞り込み)
on retFullPathWithinAFolderWithRecursiveFilterByExtAndFileNameString(aFol, aExtList, aNameString)
  set anArray to NSMutableArray’s array()
  
set aPath to NSString’s stringWithString:aFol
  
set dirEnum to NSFileManager’s defaultManager()’s enumeratorAtPath:aPath
  
  
repeat
    set aName to (dirEnum’s nextObject())
    
if aName = missing value then exit repeat
    
set aFullPath to aPath’s stringByAppendingPathComponent:aName
    
anArray’s addObject:aFullPath
  end repeat
  
  
set thePred to NSPredicate’s predicateWithFormat:“pathExtension IN [c]%@ && lastPathComponent CONTAINS %@” argumentArray:{aExtList, aNameString}
  
set bArray to anArray’s filteredArrayUsingPredicate:thePred
  
  
return bArray as list
end retFullPathWithinAFolderWithRecursiveFilterByExtAndFileNameString

★Click Here to Open This Script 

AppleScript名:指定フォルダ以下のすべてのファイルを再帰で取得(spotlightで処理).scpt
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions
use mdLib : script “Metadata Lib” version “1.0.0″
–http://piyocast.com/as/archives/4796

–set theFolder to choose folder
set theFolder to (path to desktop folder)
set theFiles to mdLib’s searchFolders:{theFolder} searchString:“kMDItemContentType IN[c] %@” searchArgs:{“com.apple.applescript.script”, “com.apple.applescript.script-bundle”}

★Click Here to Open This Script 

2017/08/31 指定フォルダ内の指定文字列を名称に含むファイルを抽出する v3

指定フォルダ内に存在するファイルを名称の一部に含むキーワードで抽出するAppleScriptです。NSFileManager経由で高速に処理します。結果はPOSIX pathのlistで返します。

前バージョンのScriptに対してShane Stanleyからツッコミが入って、

(1)as «class furl»で結果を返す(fileのlistとして結果が返る)処理はmacOS 10.10.xでは結果が正しくBridgeされないので、もう少し丁寧に処理するか10.10を対象外に

(2)ループ処理について、AppleScriptで処理可能な常識的な範囲のアイテム数(高速化処理をしていなければ数千程度)の範囲であればrepeat with in のループ処理の方がobjectEnumeratorでループするより速い(数十万〜数百万アイテムの場合にはobjectEnumerator)

(3)Package File(実体はフォルダ)についても区別する/しない処理を行なったほうがいい

ということで修正してみました。指定フォルダから、指定文字列を名称に含むファイルを抽出する処理は、登場頻度もたいへん高いので、Finder経由ではなくこうしたCocoaの機能を利用するルーチンにさしかえると大幅な処理速度向上が見込まれます。とくに、非力なマシンでの速度向上が期待されます。

個人的には、macOS 10.10.xはScripting Bridge系のバグやFolder Actionのバグなど問題が多かったので、なるべく対象外にしたい気持ちでいっぱいです。

AppleScript名:指定フォルダ内の指定文字列を名称に含むファイルを抽出する v3
– Created 2017-08-28 by Takaaki Naganoya
– Modified 2017-08-29 by Shane Stanley
– Modified 2017-08-30 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4795

property NSURLIsDirectoryKey : a reference to current application’s NSURLIsDirectoryKey
property NSDirectoryEnumerationSkipsHiddenFiles : a reference to current application’s NSDirectoryEnumerationSkipsHiddenFiles
property NSPredicate : a reference to current application’s NSPredicate
property NSMutableArray : a reference to current application’s NSMutableArray
property NSDirectoryEnumerationSkipsPackageDescendants : a reference to current application’s NSDirectoryEnumerationSkipsPackageDescendants
property NSFileManager : a reference to current application’s NSFileManager
property |NSURL| : a reference to current application’s |NSURL|
property NSDirectoryEnumerationSkipsSubdirectoryDescendants : a reference to current application’s NSDirectoryEnumerationSkipsSubdirectoryDescendants

set sourceFolder to POSIX path of (choose folder)
set aKeyword to “スクリーン”

set file1aRes to my filterOutFilesByName:aKeyword fromDirectory:sourceFolder exceptPackages:false
set file1bRes to my filterOutFilesByNameFromDirectory(aKeyword, sourceFolder, false)
–>  {”/Users/me/Desktop/スクリーンショット 2017-04-15 13.18.03.png”, ….”/Users/me/Desktop/スクリーンテスト.scptd”}

set file2Res to my filterOutFilesByName:aKeyword fromDirectory:sourceFolder exceptPackages:true
set file2bRes to my filterOutFilesByNameFromDirectory(aKeyword, sourceFolder, true)
–>  {”/Users/me/Desktop/スクリーンショット 2017-04-15 13.18.03.png”, ….}

–Get files

–指定フォルダ内の指定文字列を含むファイル名のファイルをPOSIX pathのlistで抽出する
on filterOutFilesByName:(fileNameStr as string) fromDirectory:(sourceFolder) exceptPackages:(packageF as boolean)
  set fileManager to NSFileManager’s defaultManager()
  
set aURL to |NSURL|’s fileURLWithPath:sourceFolder
  
set theOptions to ((NSDirectoryEnumerationSkipsPackageDescendants) as integer) + ((NSDirectoryEnumerationSkipsHiddenFiles) as integer) + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) as integer)
  
set directoryContents to fileManager’s contentsOfDirectoryAtURL:aURL includingPropertiesForKeys:{} options:theOptions |error|:(missing value)
  
set findPredicates to NSPredicate’s predicateWithFormat_(“lastPathComponent CONTAINS %@”, fileNameStr)
  
set foundItemList to directoryContents’s filteredArrayUsingPredicate:findPredicates
  
  
–Remove Folders From found URL Array
  
set anArray to NSMutableArray’s alloc()’s init()
  
repeat with i in foundItemList
    set j to contents of i
    
set {theResult, isDirectory} to (j’s getResourceValue:(reference) forKey:(NSURLIsDirectoryKey) |error|:(missing value))
    
    
–Collect files
    
if (isDirectory as boolean = false) then
      (anArray’s addObject:j)
      
    else if (packageF = false) then
      –Allow Package files?
      
set {theResult, isPackage} to (j’s getResourceValue:(reference) forKey:(current application’s NSURLIsPackageKey) |error|:(missing value))
      
if (isPackage as boolean) = true then
        (anArray’s addObject:j)
      end if
    end if
    
  end repeat
  
  
return (anArray’s valueForKey:“path”) as list
end filterOutFilesByName:fromDirectory:exceptPackages:

on filterOutFilesByNameFromDirectory(fileNameStr as string, fromDir, packageF as boolean)
  return my filterOutFilesByName:(fileNameStr as string) fromDirectory:fromDir exceptPackages:(packageF as boolean)
end filterOutFilesByNameFromDirectory

★Click Here to Open This Script 

2017/08/28 指定フォルダ内の指定文字列を名称に含むファイル、フォルダを抽出する v2

指定フォルダ内に存在するファイル/フォルダを名称の一部に含むキーワードで抽出するAppleScriptです。NSFileManager経由で高速に処理します。

指定フォルダ内のファイルやフォルダを求める処理は、初歩の初歩で行うものですが、macOS 10.6以降はFinder経由でこれを行うと処理速度が落ちるようになってきました。Finderのチューニングの問題だと思うのですが、AppleScriptからの問い合わせに対してあまり高速に結果を返さないようになってきました(時期的に見ると、ちょうどFinderがCocoa化されたタイミング。Finderのパフォーマンスを確保するための技術的な問題があったのでは?)。

macOS 10.6当時は「ゆくゆくはFile処理はすべてSystem Eventsに移管するのではないか?」と感じていましたが、どうもこのプランは立ち消えになったようで、Finder TabやらFinder TagがFinderのAppleScript用語辞書に実装されないまま、うやむやに。

今日、Finder経由でのファイル情報の取得はとてもコスト(=処理時間)のかかる処理になってきました(条件を何も指定しなければそれなりの速度は出ます。条件指定を行うとちょっと、、、)。逆に、ファイルの移動や削除については大幅に速度が落ちるといったことはありません。

さらに、macOS 10.13で試してみると、Finder経由で指定文字列をファイル名に含むファイルの抽出が遅くなっています(as alias listで処理結果をcastしても遅い)。数千ファイル存在するようなフォルダで実行すると、かーなり遅いです(4,300ファイルのフォルダに対して実行してみたら、SSD搭載機であってもAppleEvent Time Out(=180 sec)にひっかかるぐらい待たされる)。

# 搭載RAM 4GBのMacBook Air 2011でmacOS 10.13betaを試しているので、メモリが少なすぎてパフォーマンス低下をきたしている可能性もあります。ねんのため。

macOS 10.13上でもshell scriptやCocoa経由でのAppleScript処理は問題なく速いので、実用的な速度のAppleScriptを書こうとしたら「なるべくFinder経由でファイル処理を行わない」のがセオリーになりつつあります。

さらに、Xcode上のCocoa AppleScript appletでも各種ファイル処理(System Events経由で特定フォルダへのパスを求めるような気軽な処理)が実行できないケースもあります。指定フォルダ内のファイル/フォルダの一覧取得については、NSFileManager経由で処理することが妥当だと感じるようになりました。

処理結果についてはfileのリストで返すものとPOSIX pathで返すものを用意してみました。fileで返しておけばaliasに型変換してGUIアプリケーションに渡せますし、POSIX pathで返しておけばCocoa系のAPI経由での処理との相性がいいところです。

AppleScript名:指定フォルダ内の指定文字列を名称に含むファイル、フォルダを抽出する v2
– Created 2017-08-28 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4794

property NSURLIsDirectoryKey : a reference to current application’s NSURLIsDirectoryKey
property NSDirectoryEnumerationSkipsHiddenFiles : a reference to current application’s NSDirectoryEnumerationSkipsHiddenFiles
property NSPredicate : a reference to current application’s NSPredicate
property NSMutableArray : a reference to current application’s NSMutableArray
property NSDirectoryEnumerationSkipsPackageDescendants : a reference to current application’s NSDirectoryEnumerationSkipsPackageDescendants
property NSFileManager : a reference to current application’s NSFileManager
property |NSURL| : a reference to current application’s |NSURL|
property NSDirectoryEnumerationSkipsSubdirectoryDescendants : a reference to current application’s NSDirectoryEnumerationSkipsSubdirectoryDescendants

set sourceFolder to POSIX path of (choose folder)
set aKeyword to “スクリーン”

set file1Res to my filterOutFilesByFileName:aKeyword fromDirectory:sourceFolder
–>  {file “Cherry:Users:maro:Desktop:スクリーンショット 2017-04-15 13.18.03.png”, file “Cherry:Users:maro:Desktop:スクリーンショット 2017-04-15 13.48.44.png”, …}
set file2Res to my filterOutPOSIXPathsByFileName:aKeyword fromDirectory:sourceFolder
–>  {”/Users/maro/Desktop/スクリーンショット 2017-04-15 13.18.03.png”, “/Users/maro/Desktop/スクリーンショット 2017-04-15 13.48.44.png”, ….}

set folder1Res to my filterOutFoldersByFolderName:aKeyword fromDirectory:sourceFolder
–>  {file “Cherry:Users:maro:Desktop:スクリーン”}
set folder2Res to my filterOutDirPOSIXPathsByFolderName:aKeyword fromDirectory:sourceFolder
–>  {”/Users/maro/Desktop/スクリーン”}

–Get files

–指定フォルダ内の指定文字列を含むファイル名のファイルをfileのlistで抽出する
on filterOutFilesByFileName:fileNameStr fromDirectory:sourceFolder
  set fileManager to NSFileManager’s defaultManager()
  
set aURL to |NSURL|’s fileURLWithPath:sourceFolder
  
set theOptions to ((NSDirectoryEnumerationSkipsPackageDescendants) as integer) + ((NSDirectoryEnumerationSkipsHiddenFiles) as integer) + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) as integer)
  
set directoryContents to fileManager’s contentsOfDirectoryAtURL:aURL includingPropertiesForKeys:{} options:theOptions |error|:(missing value)
  
set findPredicates to NSPredicate’s predicateWithFormat_(“lastPathComponent CONTAINS %@”, fileNameStr)
  
set foundItemList to directoryContents’s filteredArrayUsingPredicate:findPredicates
  
  
–Remove Folders From found URL Array
  
set theEnumerator to foundItemList’s objectEnumerator()
  
set anArray to NSMutableArray’s alloc()’s init()
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue = missing value then exit repeat
    
set {theResult, isDirectory} to aValue’s getResourceValue:(reference) forKey:(NSURLIsDirectoryKey) |error|:(missing value)
    
if (isDirectory as boolean) = false then
      anArray’s addObject:(aValue’s |path|() as «class furl»)
    end if
  end repeat
  
  
return anArray as list
end filterOutFilesByFileName:fromDirectory:

–指定フォルダ内の指定文字列を含むファイル名のファイルをPOSIX pathのlistで抽出する
on filterOutPOSIXPathsByFileName:fileNameStr fromDirectory:sourceFolder
  set fileManager to NSFileManager’s defaultManager()
  
set aURL to |NSURL|’s fileURLWithPath:sourceFolder
  
set theOptions to ((NSDirectoryEnumerationSkipsPackageDescendants) as integer) + ((NSDirectoryEnumerationSkipsHiddenFiles) as integer) + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) as integer)
  
  
set directoryContents to fileManager’s contentsOfDirectoryAtURL:aURL includingPropertiesForKeys:{} options:theOptions |error|:(missing value)
  
set findPredicates to NSPredicate’s predicateWithFormat_(“lastPathComponent CONTAINS %@”, fileNameStr)
  
set foundItemList to directoryContents’s filteredArrayUsingPredicate:findPredicates
  
  
–Remove Folders From found URL Array
  
set theEnumerator to foundItemList’s objectEnumerator()
  
set anArray to NSMutableArray’s alloc()’s init()
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue = missing value then exit repeat
    
set {theResult, isDirectory} to aValue’s getResourceValue:(reference) forKey:(NSURLIsDirectoryKey) |error|:(missing value)
    
if (isDirectory as boolean) = false then
      anArray’s addObject:(aValue’s |path|())
    end if
  end repeat
  
  
return anArray as list
end filterOutPOSIXPathsByFileName:fromDirectory:

–Get folders

–指定フォルダ内の指定文字列を含むフォルダ名のフォルダをfileのlistで抽出する
on filterOutFoldersByFolderName:folderNameStr fromDirectory:sourceFolder
  set fileManager to NSFileManager’s defaultManager()
  
set aURL to |NSURL|’s fileURLWithPath:sourceFolder
  
set theOptions to ((NSDirectoryEnumerationSkipsPackageDescendants) as integer) + ((NSDirectoryEnumerationSkipsHiddenFiles) as integer) + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) as integer)
  
set directoryContents to fileManager’s contentsOfDirectoryAtURL:aURL includingPropertiesForKeys:{} options:theOptions |error|:(missing value)
  
set findPredicates to NSPredicate’s predicateWithFormat_(“lastPathComponent CONTAINS %@”, folderNameStr)
  
set foundItemList to directoryContents’s filteredArrayUsingPredicate:findPredicates
  
  
–Remove Folders From found URL Array
  
set theEnumerator to foundItemList’s objectEnumerator()
  
set anArray to NSMutableArray’s alloc()’s init()
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue = missing value then exit repeat
    
set {theResult, isDirectory} to aValue’s getResourceValue:(reference) forKey:(NSURLIsDirectoryKey) |error|:(missing value)
    
if (isDirectory as boolean) = true then
      anArray’s addObject:(aValue’s |path|() as «class furl»)
    end if
  end repeat
  
  
return anArray as list
end filterOutFoldersByFolderName:fromDirectory:

–指定フォルダ内の指定文字列を含むフォルダ名のフォルダをPOSIX pathのlistで抽出する
on filterOutDirPOSIXPathsByFolderName:folderNameStr fromDirectory:sourceFolder
  set fileManager to NSFileManager’s defaultManager()
  
set aURL to |NSURL|’s fileURLWithPath:sourceFolder
  
set theOptions to ((NSDirectoryEnumerationSkipsPackageDescendants) as integer) + ((NSDirectoryEnumerationSkipsHiddenFiles) as integer) + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) as integer)
  
  
set directoryContents to fileManager’s contentsOfDirectoryAtURL:aURL includingPropertiesForKeys:{} options:theOptions |error|:(missing value)
  
set findPredicates to NSPredicate’s predicateWithFormat_(“lastPathComponent CONTAINS %@”, folderNameStr)
  
set foundItemList to directoryContents’s filteredArrayUsingPredicate:findPredicates
  
  
–Remove Folders From found URL Array
  
set theEnumerator to foundItemList’s objectEnumerator()
  
set anArray to NSMutableArray’s alloc()’s init()
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue = missing value then exit repeat
    
set {theResult, isDirectory} to aValue’s getResourceValue:(reference) forKey:(NSURLIsDirectoryKey) |error|:(missing value)
    
if (isDirectory as boolean) = true then
      anArray’s addObject:(aValue’s |path|())
    end if
  end repeat
  
  
return anArray as list
end filterOutDirPOSIXPathsByFolderName:fromDirectory:

★Click Here to Open This Script 

2017/08/28 数値文字列に対して、少数点以下の数値の切り上げ、切り下げ v2

数値文字列(numeric string)に対して、少数点以下の数値の切り上げ、切り下げを行うAppleScriptの改修版です(Thanks Shane!)。

REST APIから受け取ることの多い数値文字列(Numeric String)の切り下げを行うために作成したものですが、Shaneいわく「floatよりもdoubleのほうが精度が高いのでdoubleのほうがいいよ」とのこと。

また、stringFromNumber:で直接doubleValueを渡して大丈夫とのことだったので、いろいろ試しつつ(変更前と比較しつつ)、だいたいの目的である小数点以下2桁程度の数値文字列の切り上げについては挙動に変更はありません。

AppleScript名:数値文字列に対して、少数点以下の数値の切り上げ、切り下げ v2
– Created 2017-08-12 by Takaaki Naganoya
– Modified 2017-08-28 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4793

property NSString : a reference to current application’s NSString
property NSNumber : a reference to current application’s NSNumber
property NSNumberFormatter : a reference to current application’s NSNumberFormatter
property NSNumberFormatterRoundUp : a reference to current application’s NSNumberFormatterRoundUp
property NSNumberFormatterRoundDown : a reference to current application’s NSNumberFormatterRoundDown

set a to “0.9096617698669434″
set a1Res to roundingDownNumStr(a, 2) of me

set b to “0.0001830748806241899″
set b1Res to roundingDownNumStr(b, 2) of me

return {a1Res, b1Res}
–>  {0.9, 0.0}

on roundingDownNumStr(aNum as string, aDigit as integer)
  set a to NSString’s stringWithString:aNum
  
set aa to a’s doubleValue()
  
set aFormatter to NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setMaximumFractionDigits:aDigit
  
aFormatter’s setRoundingMode:(NSNumberFormatterRoundDown)
  
set aStr to aFormatter’s stringFromNumber:aa
  
return (aStr as text) as real
end roundingDownNumStr

on roundingUpNumStr(aNum as string, aDigit as integer)
  set a to NSString’s stringWithString:aNum
  
set aa to a’s doubleValue()
  
set aFormatter to NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setMaximumFractionDigits:aDigit
  
aFormatter’s setRoundingMode:(NSNumberFormatterRoundUp)
  
set aStr to aFormatter’s stringFromNumber:aa
  
return (aStr as text) as real
end roundingUpNumStr

★Click Here to Open This Script 

2017/08/27 文の意味類似度の評価

Apitore「文の意味類似度の評価」APIを呼び出して、文単位で意味的な近さを評価するAppleScriptです。

ApitoreのREST API呼び出しのために、Apitoreにユーザー登録してAccess Tokenを取得し、retAccessToken()内に記述しておいてください(自分はKeychainにAccessTokenを記録して呼び出すライブラリを利用しています)。

掲載リストそのままでAccess Tokenを記入していない状態だとエラーになります。

「文の意味類似度の評価」APIはWord2Vecを用いているわけですが、Word2Vecの処理のためにWikipediaのダンプ内容を学習させており、ローカルでもWord2Vecの演算を行いたいところ。もう、いっそのことOSにWikipediaのローカルダンプを含めて配布してほしい今日このごろです(OSインストーラのサイズが倍ぐらいになりそうですが)。

AppleScript名:文の意味類似度の評価
– Created 2017-08-16 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.5"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4792

property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSMutableData : a reference to current application’s NSMutableData
property NSNumberFormatter : a reference to current application’s NSNumberFormatter
property NSURLRequestReloadIgnoringLocalCacheData : a reference to current application’s NSURLRequestReloadIgnoringLocalCacheData
property NSURLQueryItem : a reference to current application’s NSURLQueryItem
property NSMutableURLRequest : a reference to current application’s NSMutableURLRequest
property |NSURL| : a reference to current application’s |NSURL|
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSString : a reference to current application’s NSString
property NSURLConnection : a reference to current application’s NSURLConnection
property NSURLComponents : a reference to current application’s NSURLComponents
property NSNumber : a reference to current application’s NSNumber
property NSJSONSerialization : a reference to current application’s NSJSONSerialization
property NSNumberFormatterRoundDown : a reference to current application’s NSNumberFormatterRoundDown

set targStr1 to "戦場の絆REV.4において、ガンダムはコスト250の格闘機である"
set targStr2 to "戦場の絆REV.2において、ガンダムはかつてコスト280の近距離機であった"
set aRes to calcSimiliarity(targStr1, targStr2) of me

set targStr3 to "この秋、戦場の絆REV.4にGブルとザクレロが宇宙ステージ専用機体として登場するらしい。"
set targStr4 to "バスが遅くて、非力なUプロセッサで、放熱機構が弱い、鈍足なMacBook Air 2011。"
set bRes to calcSimiliarity(targStr3, targStr4) of me

return {aRes, bRes}
–> {0.9, 0.0}–1.0に近いほど類似度が高い。0に近いほど類似度が低い

on calcSimiliarity(targStr1, targStr2)
  set reqURLStr to "https://api.apitore.com/api/53/sentence-similarity/eval"
  
set accessToken to retAccessToken() of me –Access Token
  
  
set aReq to {text1:targStr1, text2:targStr2}
  
set aRec to {access_token:accessToken}
  
set aURL to retURLwithParams(reqURLStr, aRec) of me
  
  
set aRes to callRestPOSTAPIAndParseResults(aURL, aReq) of me
  
set aRESCode to responseCode of aRes
  
set aRESHeader to responseHeader of aRes
  
set aRESTres to (json of aRes)
  
return roundingDownNumStr(aRESTres’s similarity, 2) of me
end calcSimiliarity

–POST methodのREST APIを呼ぶ
on callRestPOSTAPIAndParseResults(aURL, aReq)
  set {theData, theError} to NSJSONSerialization’s dataWithJSONObject:aReq options:0 |error|:(reference)
  
if theData is missing value then error (theError’s localizedDescription() as text) number -10000
  
set postBody to NSMutableData’s |data|()
  
postBody’s appendData:theData
  
  
–Request
  
set aRequest to NSMutableURLRequest’s requestWithURL:(|NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:"POST"
  
aRequest’s setCachePolicy:(NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:600
  
aRequest’s setValue:"application/json" forHTTPHeaderField:"Content-Type"
  
aRequest’s setHTTPBody:postBody
  
  
set aRes to NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to NSString’s alloc()’s initWithData:bRes encoding:(NSUTF8StringEncoding)
  
  
set jsonString to NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestPOSTAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

on retAccessToken()
  return "xxXXXxxX-XxXx-XXXX-xXXX-XXxXXxxXxxXx" –API Tore Access Token
end retAccessToken

on roundingDownNumStr(aNum as string, aDigit as integer)
  set a to NSString’s stringWithString:aNum
  
set aa to a’s floatValue()
  
set aFormatter to NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setMaximumFractionDigits:aDigit
  
aFormatter’s setRoundingMode:(NSNumberFormatterRoundDown)
  
set aStr to aFormatter’s stringFromNumber:(NSNumber’s numberWithFloat:aa)
  
return (aStr as text) as real
end roundingDownNumStr

★Click Here to Open This Script 

2017/08/27 数値文字列に対して、少数点以下の数値の切り上げ、切り下げ

数値文字列(numeric string)に対して、少数点以下の数値の切り上げ、切り下げを行うAppleScriptです。

REST APIで桁数の多い数値文字列を返してくるものがあるので、その変換や桁数指定のために作成したものです。指定桁での数値の切り上げ、切り下げを処理してから数値に変換しないと指数表示になっていまひとつ取り回しが良くなかったので、数値文字列の状態で切り上げ、切り下げを行うようにしてみました。

AppleScript名:数値文字列に対して、少数点以下の数値の切り上げ、切り下げ
– Created 2017-08-12 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4791

property NSString : a reference to current application’s NSString
property NSNumber : a reference to current application’s NSNumber
property NSNumberFormatter : a reference to current application’s NSNumberFormatter
property NSNumberFormatterRoundUp : a reference to current application’s NSNumberFormatterRoundUp
property NSNumberFormatterRoundDown : a reference to current application’s NSNumberFormatterRoundDown

set a to “0.9096617698669434″
set a1Res to roundingDownNumStr(a, 2) of me
–>  0.9

set b to “0.0001830748806241899″
set b1Res to roundingDownNumStr(b, 2) of me
–>  0.0

on roundingDownNumStr(aNum as string, aDigit as integer)
  set a to NSString’s stringWithString:aNum
  
set aa to a’s floatValue()
  
set aFormatter to NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setMaximumFractionDigits:aDigit
  
aFormatter’s setRoundingMode:(NSNumberFormatterRoundDown)
  
set aStr to aFormatter’s stringFromNumber:(NSNumber’s numberWithFloat:aa)
  
return (aStr as text) as real
end roundingDownNumStr

on roundingUpNumStr(aNum as string, aDigit as integer)
  set a to NSString’s stringWithString:aNum
  
set aa to a’s floatValue()
  
set aFormatter to NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setMaximumFractionDigits:aDigit
  
aFormatter’s setRoundingMode:(NSNumberFormatterRoundUp)
  
set aStr to aFormatter’s stringFromNumber:(NSNumber’s numberWithFloat:aa)
  
return (aStr as text) as real
end roundingUpNumStr

★Click Here to Open This Script 

2017/08/23 指定フォルダ以下のLocalのHTMLのテキストエンコーディングをUTF-8に書き換えて保存

Localの指定フォルダ以下にあるHTMLをすべてSpotlightで抽出し、見つかったすべてのHTMLのテキストエンコーディングをUTF-8に変更して上書き保存するAppleScriptです。

本Script単体では動作確認できないので、確認のためにアプレットを用意しておきました。HTMLへの要素アクセスにはオープンソースのHTMLReader.framework(By Nolan Waite)を、Spotlight検索にはShane Stanleyの「Metadata Lib」を、日本語テキストファイルのエンコーディング自動検出には自作の「japaneseTextEncodingDetector」を用いています。これらをすべて含んだアプレット「htmlutf8rewriter.app」を用意しておきました(Code Signずみ)。

–> Download htmlutf8rewriter.zip

実際にテストデータ(7,979 files)で実験してみたところ、MacBook Air 2011(Core i5, 1.6GHz)上で702 Seconds、12分で処理できました(macOS 10.13beta 7で実行)。

バスが遅くて、非力なUプロセッサで、放熱機構が弱い、鈍足なMacBook Air 2011でこの程度の速度なので、手元のMacBook Pro 2012で実行すると半分ぐらいの時間で実行できるものと思われます。

Dual Core(4 thread)のMacBook Airでも1〜2thread分は処理に余裕があったので、よりコア数の多いMac上で実行する場合には並列実行するとマシンパワーを絞り出せるものと思われます。

用意したテストデータには行儀の悪いものも混在していたので、300程度のエラーが発生。こうしたエラーデータに対しては、別途何らかのテキストエディタをコントロールして書き換え&保存を行なってもよいのかもしれません。

最初からテキストエディタをコントロールして書き換えを行うと並列処理できないですし、AppleScript単体で実行するよりも処理時間がかかります(現状だと秒間11ファイルぐらい処理できていますが、外部のテキストエディタを制御して処理すると秒間2ファイルぐらいまで落ちるはずです)。

AppleScript名:指定フォルダ以下のLocalのHTMLのテキストエンコーディングをUTF-8に書き換えて保存
– Created 2017-08-23 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “HTMLReader” –https://github.com/nolanw/HTMLReader
use jLib : script “japaneseTextEncodingDetector”
use mdLib : script “Metadata Lib” version “1.0.0″
–http://piyocast.com/as/archives/4789

property NSString : a reference to current application’s NSString
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property HTMLDocument : a reference to current application’s HTMLDocument

set theFolder to choose folder
set theRecord to mdLib’s searchFolders:{theFolder} searchString:“kMDItemFSName ENDSWITH[c] ’.html’” searchArgs:{}
set aCount to 1

repeat with i in theRecord
  set hRes to overWriteHTMLWithUTF8Encoding(i) of me
  
if hRes = true then
    set aCount to aCount + 1
  end if
end repeat

return aCount

on overWriteHTMLWithUTF8Encoding(aPOSIX)
  set aRes to readJapanesTextFileWithGuessingEncoding(aPOSIX) of jLib
  
set bRes to retSpecifiedElementsFromHTMLString(aRes, “meta”) of me
  
–>  {(HTMLElement) 0 children>, (HTMLElement) 0 children>, ….
  
  
–Search Element
  
set aCount to 0
  
set hitF to false
  
repeat with i in bRes
    set tRes to i’s attributes()
    
–> (NSDictionary) {http-equiv:”Content-Type”, content:”text/html; charset=euc-jp”}
    
    
set kList to tRes’s allKeys() as list
    
–> {”http-equiv”, “content”}
    
    
if kList contains “http-equiv” then
      set hitF to true
      
exit repeat
    end if
    
    
set aCount to aCount + 1
  end repeat
  
  
if hitF = false then return false –Element Not Found
  
  
–Text Encoding を書き換えたHTMLElementを作成する
  
set aHTML to HTMLDocument’s documentWithString:aRes
  
set anElement to (aHTML’s nodesMatchingSelector:“meta”)’s objectAtIndex:aCount
  
anElement’s setObject:“text/html; charset=UTF-8″ forKeyedSubscript:“content”
  
set aRootHTMLStr to anElement’s |document|()’s serializedFragment()
  
  
–Overwrite HTML
  
set aRes to (aRootHTMLStr’s writeToFile:aPOSIX atomically:false encoding:(NSUTF8StringEncoding) |error|:(missing value)) as boolean
  
return aRes
end overWriteHTMLWithUTF8Encoding

–与えられたHTML文字列のうち、指定されたタグ要素で囲まれた文字要素を返す
on retSpecifiedElementsFromHTMLString(anNSString, aTag)
  set aHTML to HTMLDocument’s documentWithString:anNSString
  
set anElement to (aHTML’s nodesMatchingSelector:aTag)
  
return anElement as list
end retSpecifiedElementsFromHTMLString

–与えられたHTML文字列のうち、指定されたタグ要素で囲まれた文字要素を返す
on retSpecifiedElementStringFromHTMLString(anNSString, aTag)
  set aHTML to HTMLDocument’s documentWithString:anNSString
  
set anElement to (aHTML’s nodesMatchingSelector:aTag)’s firstObject()’s textContent()
  
return anElement as string
end retSpecifiedElementStringFromHTMLString

–指定されたローカルのHTMLファイルを、指定文字エンコーディングで読み込んで、指定されたタグ要素で囲まれた文字要素を返す
on retSpecifiedElementStringFromLocalHTML(aPOSIX, anEncoding, aTag)
  set aPath to NSString’s stringWithString:aPOSIX
  
set aData to NSString’s stringWithContentsOfFile:aPath encoding:(anEncoding) |error|:(missing value)
  
if aData = missing value then return false
  
  
set aHTML to HTMLDocument’s documentWithString:aData
  
set anElement to (aHTML’s nodesMatchingSelector:aTag)’s firstObject()’s textContent()
  
return anElement as string
end retSpecifiedElementStringFromLocalHTML

★Click Here to Open This Script 

2017/08/22 CIFilterで画像切り抜き

CIFilterで画像の切り抜きを行うAppleScriptです。指定画像と同一階層のフォルダにUUID.pngの切り抜き画像を作成します。

Cocoaでの画像切り抜きは、

NSBitmapImageRepで切り抜き元画像の指定範囲のBitmapを取得して、新規NSBitmapImageRepに描画して結果を取得

というのが常識的なやり方のようですが、CIFilterの機能を用いて切り抜きできることを知ったので、比較のために実行してみました。

crop100images.png

結局、CIFilterの方が20倍ぐらい遅かったので、NSBitmapImageRepを使うやり方で問題ないことがわかりました。NSImageを100回指定範囲で切り抜くのに、NSBitmapImageRepで0.10950499773秒(1回あたり0.001秒)、このCIFilter経由だと1.957534968853秒(1回あたり0.02秒)でした(@MacBook Pro Retina 2012 Core i7 2.66GHz)。

もしかしたら、最近のGPU性能が向上したマシンだとこの差が小さくなるのかもしれません(CPU性能はたいして向上していない一方で、GPU性能は大きく向上しているはずなので)。

AppleScript名:CIFilterで画像切り抜き
– Created 2017-08-22 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “QuartzCore”
–http://piyocast.com/as/archives/4788

property CIFilter : a reference to current application’s CIFilter
property CIVector : a reference to current application’s CIVector
property CIImage : a reference to current application’s CIImage
property NSString : a reference to current application’s NSString
property NSUUID : a reference to current application’s NSUUID
property NSPNGFileType : a reference to current application’s NSPNGFileType
property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep
property NSImage : a reference to current application’s NSImage
property |NSURL| : a reference to current application’s |NSURL|

–画像を選択
set aPath to POSIX path of (choose file of type {“public.image”})

set aNSImage to NSImage’s alloc()’s initWithContentsOfFile:aPath
set aSize to aNSImage’s |size|()
set aWidth to aSize’s width()
set aHeight to aSize’s height()

–Crop Image by CIFilter
set cropRes to cropNSImageWithCIFilter(aNSImage, 0, (aHeight - 100), 100, 100) of me

–Save as PNG
set fRes to retUUIDfilePath(aPath, “png”) of me
set sRes to saveNSImageAtPathAsPNG(cropRes, fRes) of me

–NSImageをCIImageに変換してCIfilterを実行
on cropNSImageWithCIFilter(aNSImage, x1, y1, xWidth, yHeight)
  set aCIImage to convNSImageToCIimage(aNSImage) of me
  
set aFilter to CIFilter’s filterWithName:“CICrop”
  
set aCIVector to CIVector’s vectorWithX:x1 Y:y1 Z:xWidth W:yHeight
  
  
aFilter’s setDefaults()
  
aFilter’s setValue:aCIImage forKey:“inputImage”
  
aFilter’s setValue:aCIVector forKey:“inputRectangle”
  
  
set filteredImage to (aFilter’s outputImage)
  
set newNSImage to convCIimageToNSImage(filteredImage) of me
  
  
return newNSImage
end cropNSImageWithCIFilter

on convCIimageToNSImage(aCIImage)
  set aRep to NSBitmapImageRep’s alloc()’s initWithCIImage:aCIImage
  
set tmpSize to aRep’s |size|()
  
set newImg to NSImage’s alloc()’s initWithSize:tmpSize
  
newImg’s addRepresentation:aRep
  
return newImg
end convCIimageToNSImage

on convNSImageToCIimage(aNSImage)
  set tiffDat to aNSImage’s TIFFRepresentation()
  
set aRep to NSBitmapImageRep’s imageRepWithData:tiffDat
  
set newImg to CIImage’s alloc()’s initWithBitmapImageRep:aRep
  
return newImg
end convNSImageToCIimage

on retUUIDfilePath(aPath, aEXT)
  set aUUIDstr to (NSUUID’s UUID()’s UUIDString()) as string
  
set aPath to ((NSString’s stringWithString:aPath)’s stringByDeletingLastPathComponent()’s stringByAppendingPathComponent:aUUIDstr)’s stringByAppendingPathExtension:aEXT
  
return aPath
end retUUIDfilePath

–NSImageを指定パスにPNG形式で保存
on saveNSImageAtPathAsPNG(anImage, outPath)
  set imageRep to anImage’s TIFFRepresentation()
  
set aRawimg to NSBitmapImageRep’s imageRepWithData:imageRep
  
set pathString to NSString’s stringWithString:outPath
  
set newPath to pathString’s stringByExpandingTildeInPath()
  
set myNewImageData to (aRawimg’s representationUsingType:(NSPNGFileType) |properties|:(missing value))
  
set aRes to (myNewImageData’s writeToFile:newPath atomically:true) as boolean
  
return aRes –true/false
end saveNSImageAtPathAsPNG

★Click Here to Open This Script 

2017/08/21 画像の重ね合わせ

背景画像の上に指定画像を重ね合わせるAppleScriptです。

画像の重ね合わせは割と必要な場合が多い処理です。

一番必要なのは、CIFilterで画像の一部にフィルタリング処理を行うとき、特定矩形内にのみフィルタ処理するようになっていないので、処理対象部分の切り抜きを行なっておいて、切り抜いた部分に対してフィルタ処理を行い、元画像に重ね合わせるようなケースでしょうか。

Photoshop上だと造作もない選択範囲へのフィルタ実行ですが、OSの機能を用いてゼロから作ると、やや苦労させられるところです。ただ、Photoshopは並列処理に使えないので、こうした部品でフィルタ処理を行うことになることでしょう。

54519d9c-d338-4327-8703-98c21e8823ff.png
▲背景画像の上に指定画像を重ね合わせた処理結果

AppleScript名:画像の重ね合わせ
– Created 2017-08-21 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4785

property NSString : a reference to current application’s NSString
property NSUUID : a reference to current application’s NSUUID
property NSPNGFileType : a reference to current application’s NSPNGFileType
property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep
property NSImage : a reference to current application’s NSImage
property |NSURL| : a reference to current application’s |NSURL|

set aRect to {0, 0, 100, 100} –x1,y1,x-width, y-height

–Background Image
set aFile to POSIX path of (choose file with prompt “Choose background image”)
set aURL to |NSURL|’s fileURLWithPath:aFile
set aImage to NSImage’s alloc()’s initWithContentsOfURL:aURL

–Compose Image
set bFile to POSIX path of (choose file with prompt “Choose Overlay image”)
set bURL to |NSURL|’s fileURLWithPath:bFile
set bImage to NSImage’s alloc()’s initWithContentsOfURL:bURL

–Compose Two Images
set nImage to composeImage(aImage, bImage, aRect) of me

–Save as PNG
set fRes to retUUIDfilePath(aFile, “png”) of me
set sRes to saveNSImageAtPathAsPNG(nImage, fRes) of me

–2つのNSImageを重ね合わせ合成してNSImageで返す
on composeImage(backImage, composeImage, aTargerRect)
  set newImage to NSImage’s alloc()’s initWithSize:(backImage’s |size|())
  
  
copy aTargerRect to {x1, y1, x2, y2}
  
set bRect to current application’s NSMakeRect(x1, y1, x2, y2)
  
  
newImage’s lockFocus()
  
  
set newImageRect to current application’s CGRectZero
  
set newImageRect’s |size| to (newImage’s |size|)
  
  
backImage’s drawInRect:newImageRect
  
composeImage’s drawInRect:bRect
  
  
newImage’s unlockFocus()
  
return newImage
end composeImage

on retUUIDfilePath(aPath, aEXT)
  set aUUIDstr to (NSUUID’s UUID()’s UUIDString()) as string
  
set aPath to ((NSString’s stringWithString:aPath)’s stringByDeletingLastPathComponent()’s stringByAppendingPathComponent:aUUIDstr)’s stringByAppendingPathExtension:aEXT
  
return aPath
end retUUIDfilePath

–NSImageを指定パスにPNG形式で保存
on saveNSImageAtPathAsPNG(anImage, outPath)
  set imageRep to anImage’s TIFFRepresentation()
  
set aRawimg to NSBitmapImageRep’s imageRepWithData:imageRep
  
set pathString to NSString’s stringWithString:outPath
  
set newPath to pathString’s stringByExpandingTildeInPath()
  
set myNewImageData to (aRawimg’s representationUsingType:(NSPNGFileType) |properties|:(missing value))
  
set aRes to (myNewImageData’s writeToFile:newPath atomically:true) as boolean
  
return aRes –true/false
end saveNSImageAtPathAsPNG

★Click Here to Open This Script 

2017/08/19 PDFの指定ページを削除 v4(複数ページ一括指定)

指定PDF書類のうちの指定ページをまとめて削除するAppleScriptです。

ページ指定にプラスの数値を指定すると絶対ページ数、マイナスの数値を指定するとページ末尾からの相対ページ数として解釈されます。ページ削除前に削除対象のページ数をすべて絶対ページに変換しつつ、重複分を削除し、削除対象ページを降順ソートします。

常識的な範囲内では、PDFからの指定ページ削除は行えるはずです。

ただ、この程度の実装だとすべてのPDFを対象にできないので困ります。Mac App Storeで販売中のアプリ「Double PDF」ではこのあたりの問題を解決したPDF処理ルーチンを仕込んであります。

AppleScript名:PDFの指定ページを削除 v4(複数ページ一括指定)
– Modified 2017-08-19 by Takaaki Naganoya
–Original By Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “Quartz”
–http://piyocast.com/as/archives/4784

property NSSortDescriptor : a reference to current application’s NSSortDescriptor
property NSArray : a reference to current application’s NSArray
property NSSet : a reference to current application’s NSSet
property |NSURL| : a reference to current application’s |NSURL|
property PDFDocument : a reference to current application’s PDFDocument

set inFile to (choose file of type {“pdf”} with prompt “Choose your PDF files:”)
set targPageList to {1, 3, 5, 7, -1, -2}

set pRes to removeSpecificPagesFromPDF(inFile, targPageList) of me

–指定PDF書類の複数ページの一括削除
on removeSpecificPagesFromPDF(inFileAlias, targPageNumList as list)
  set inNSURL to |NSURL|’s fileURLWithPath:(POSIX path of inFileAlias)
  
set theDoc to PDFDocument’s alloc()’s initWithURL:inNSURL
  
  
–削除対象ページリストをユニーク化して降順ソート(後方から削除)
  
set pRes to theDoc’s pageCount()
  
set t3List to relativeToAbsNumList(targPageNumList, pRes) of me
  
  
repeat with i in t3List
    copy i to targPageNum
    (
theDoc’s removePageAtIndex:(targPageNum - 1))
  end repeat
  
  
–Overwrite Exsiting PDF
  
set aRes to (theDoc’s writeToURL:inNSURL) as boolean
  
  
return aRes
end removeSpecificPagesFromPDF

–絶対ページと相対ページが混在した削除対象ページリストを絶対ページに変換して重複削除して降順ソート
on relativeToAbsNumList(aList, aMax)
  set newList to {}
  
  
repeat with i in aList
    set j to contents of i
    
if i < 0 then
      set j to aMax + j
    end if
    
    
if (j aMax) and (j is not equal to 0) then
      set the end of newList to j
    end if
  end repeat
  
  
set t1List to my uniquify1DList(newList, true)
  
set t2List to my sort1DNumList:t1List ascOrder:false
  
  
return t2List
end relativeToAbsNumList

on absNum(q)
  if q is less than 0 then set q to -q
  
return q
end absNum

–1D/2D Listをユニーク化
on uniquify1DList(theList as list, aBool as boolean)
  set aArray to NSArray’s arrayWithArray:theList
  
set bArray to aArray’s valueForKeyPath:“@distinctUnionOfObjects.self”
  
return bArray as list
end uniquify1DList

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

★Click Here to Open This Script 

2017/08/19 PDFの指定ページを削除 v3(PDFDocument経由でアクセス)

指定PDF書類のうちの指定ページを削除するAppleScriptです。

以前、PDFKit中の機能のうち指定ページ(PDFPage)を削除するものがPDFPageに見つからなかったので、新規PDFDocumentを作成して元PDFの削除対象「以外の」ページをコピーして上書き保存することで擬似的にページ削除を実現していました

ページ削除機能がないのはおかしいと考え、しつこく調べていたところ….PDFPageではなくPDFDocumetにページ削除の機能が存在することに気づきました。そのため、PDFPage経由で指定のページを削除してみたものです。

skim1.png
▲実行前

skim2.png
▲実行後(7ページ目を削除した)

AppleScript名:PDFの指定ページを削除 v3(PDFDocument経由でアクセス)
– Modified 2017-08-19 by Takaaki Naganoya
–Original By Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “Quartz”
–http://piyocast.com/as/archives/4781

property |NSURL| : a reference to current application’s |NSURL|
property PDFDocument : a reference to current application’s PDFDocument

set inFile to (choose file of type {“pdf”} with prompt “Choose your PDF files:”)
set targPage to 7

set pRes to removeSpecificPageInPDF(inFile, targPage) of me

on removeSpecificPageInPDF(inFileAlias, targPageNum)
  set inNSURL to |NSURL|’s fileURLWithPath:(POSIX path of inFileAlias)
  
set theDoc to PDFDocument’s alloc()’s initWithURL:inNSURL
  
  
set pRes to theDoc’s pageCount()
  
if absNum(targPageNum) of me > pRes or targPageNum = 0 then
    error “PDF Page Range error. This PDF document has “ & (pRes as string) & ” pages. But you pointed “ & (targPageNum as string) & ” page from your script. “ & return & ” (available abs range :1…” & (pRes as string) & “, relative range: -1…-” & (pRes as string) & “)”
  end if
  
  
–Allow Relative Page Num ( -1 = the last page)
  
if targPageNum 0 then
    set targPageNum to pRes + targPageNum + 1
  end if
  
theDoc’s removePageAtIndex:(targPageNum - 1)
  
  
–Overwrite Exsiting PDF
  
set aRes to (theDoc’s writeToURL:inNSURL) as boolean
  
  
return aRes
end removeSpecificPageInPDF

on absNum(q)
  if q is less than 0 then set q to -q
  
return q
end absNum

★Click Here to Open This Script 

2017/08/17 japaneseTokenizeのじっけん

macOSの日本語形態素解析の機能を呼び出して日本語テキストをParseするCocoa Framework「japaneseTokenize.framework」を作成しました。それを呼び出すAppleScriptです。

「japaneseTokenize.framework」はゼロから書いたはじめてのObjective-Cのプログラムでもあります(ほぼコピペで作成してありますが)。Header Fileなんて書かないので、見よう見真似&XcodeのWarning Messageのいいなりで試行錯誤していました。

CFStringTokenizer経由で形態素解析してふりがな、元テキスト、ローマ字ふりがなを取得する「parseString:」と、NSLinguisticTagger経由で形態素解析して元テキスト、品詞を取得する「parseStringWithLinguisticTag:」の2つのメソッドを用意してみました。

これら2つの形態素解析機能のparseの結果が矛盾しているかどうかは未確認です(短い文章では同じことを確認してありますが、長い文章でも同じかどうかは未確認。多分同じだとは思いますけれども)。

(CFStringTokenizer)parseString:
(*これ, ら, 2, つ, の, 形態, 素, 解析, 機能, の, parse, の, 結果, が, 矛盾, し, て, いる, か, どう, か, は, 未, 確認, です, 。, 短い, 文章, で, は, 同じ, こと, を, 確認, し, て, あり, ます, が, 、, 長い, 文章, で, も, 同じ, か, どう, か, は, 未, 確認, 。, 多分, 同じ, だ, と, は, 思い, ます, けれど, も, 。*)

(NSLinguisticTagger) parseStringWithLinguisticTag:
(*これら, 2つ, の, 形態素, 解析, 機能, の, parse, の, 結果, が, 矛盾, し, て, いる, か, どう, か, は, 未, 確認, です, 。, 短い, 文章, で, は, 同じ, こと, を, 確認, し, て, あり, ます, が, 、, 長い, 文章, で, も, 同じ, か, どう, か, は, 未, 確認, 。, 多分, 同じ, だ, と, は, 思い, ます, けれど, も, 。*)

(ご参考) AppleScriptの「words of」の実行結果
(*これ, ら, 2, つ, の, 形態, 素, 解析, 機能, の, parse, の, 結果, が, 矛盾, し, て, いる, か, どう, か, は, 未, 確認, です, 短い, 文章, で, は, 同じ, こと, を, 確認, し, て, あり, ます, が, 長い, 文章, で, も, 同じ, か, どう, か, は, 未, 確認, 多分, 同じ, だ, と, は, 思い, ます, けれど, も*)

# うわ、まさかの「微妙に形態素解析結果が違っている」パターンが来た、、、(ーー; macOS 10.13 Betaで試してみたところ、CFStringTokenizer側の形態素解析結果に合わせてNSLinguisticTagger側の出力結果を修正している模様

NSLinguisticTaggerが返す品詞については、いまひとつしっくりこないですし、ユーザーが定義したユーザー定義辞書を考慮して形態素解析したりしないので、実用性についてはさっぱりな印象です。さまざまなWeb APIをAppleScriptから呼び出してみた印象からいえば、Apitoreの形態素解析APIが一番納得できる内容に見えます(Googleの形態素解析APIはまだ呼んでいません)。

jparsers.png

Yahoo!日本語形態素解析Web APIによる解析結果:
–> {ResultSet:{attributes:{xmlns:”urn:yahoo:jp:jlp”, |xsi:schemalocation|:”urn:yahoo:jp:jlp https://jlp.yahooapis.jp/MAService/V1/parseResponse.xsd”, |xmlns:xsi|:”http://www.w3.org/2001/XMLSchema-instance”}, ma_result:{total_count:{|contents|:”8″}, filtered_count:{|contents|:”8″}, word_list:{|word|:{{surface:{|contents|:”来週”}, reading:{|contents|:”らいしゅう”}, pos:{|contents|:”名詞”}}, {surface:{|contents|:”の”}, reading:{|contents|:”の”}, pos:{|contents|:”助詞”}}, {surface:{|contents|:”水曜日”}, reading:{|contents|:”すいようび”}, pos:{|contents|:”名詞”}}, {surface:{|contents|:”に”}, reading:{|contents|:”に”}, pos:{|contents|:”助詞”}}, {surface:{|contents|:”会議”}, reading:{|contents|:”かいぎ”}, pos:{|contents|:”名詞”}}, {surface:{|contents|:”を”}, reading:{|contents|:”を”}, pos:{|contents|:”助詞”}}, {surface:{|contents|:”予約”}, reading:{|contents|:”よやく”}, pos:{|contents|:”名詞”}}, {surface:{|contents|:”。”}, reading:{|contents|:”。”}, pos:{|contents|:”特殊”}}}}}}}

Apitore 日本語形態素解析【新語対応】 ipadic neologdによる解析結果:
–> {startTime:”1502971949537″, tokens:{{partOfSpeechLevel1:”名詞”, baseForm:”来週”, pronunciation:”ライシュー”, position:0, partOfSpeechLevel3:”*”, reading:”ライシュウ”, surface:”来週”, known:true, allFeatures:”名詞,副詞可能,*,*,*,*,来週,ライシュウ,ライシュー”, conjugationType:”*”, partOfSpeechLevel2:”副詞可能”, conjugationForm:”*”, allFeaturesArray:{”名詞”, “副詞可能”, “*”, “*”, “*”, “*”, “来週”, “ライシュウ”, “ライシュー”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”助詞”, baseForm:”の”, pronunciation:”ノ”, position:2, partOfSpeechLevel3:”*”, reading:”ノ”, surface:”の”, known:true, allFeatures:”助詞,連体化,*,*,*,*,の,ノ,ノ”, conjugationType:”*”, partOfSpeechLevel2:”連体化”, conjugationForm:”*”, allFeaturesArray:{”助詞”, “連体化”, “*”, “*”, “*”, “*”, “の”, “ノ”, “ノ”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”名詞”, baseForm:”水曜日”, pronunciation:”スイヨービ”, position:3, partOfSpeechLevel3:”*”, reading:”スイヨウビ”, surface:”水曜日”, known:true, allFeatures:”名詞,副詞可能,*,*,*,*,水曜日,スイヨウビ,スイヨービ”, conjugationType:”*”, partOfSpeechLevel2:”副詞可能”, conjugationForm:”*”, allFeaturesArray:{”名詞”, “副詞可能”, “*”, “*”, “*”, “*”, “水曜日”, “スイヨウビ”, “スイヨービ”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”助詞”, baseForm:”に”, pronunciation:”ニ”, position:6, partOfSpeechLevel3:”一般”, reading:”ニ”, surface:”に”, known:true, allFeatures:”助詞,格助詞,一般,*,*,*,に,ニ,ニ”, conjugationType:”*”, partOfSpeechLevel2:”格助詞”, conjugationForm:”*”, allFeaturesArray:{”助詞”, “格助詞”, “一般”, “*”, “*”, “*”, “に”, “ニ”, “ニ”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”名詞”, baseForm:”会議”, pronunciation:”カイギ”, position:7, partOfSpeechLevel3:”*”, reading:”カイギ”, surface:”会議”, known:true, allFeatures:”名詞,サ変接続,*,*,*,*,会議,カイギ,カイギ”, conjugationType:”*”, partOfSpeechLevel2:”サ変接続”, conjugationForm:”*”, allFeaturesArray:{”名詞”, “サ変接続”, “*”, “*”, “*”, “*”, “会議”, “カイギ”, “カイギ”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”助詞”, baseForm:”を”, pronunciation:”ヲ”, position:9, partOfSpeechLevel3:”一般”, reading:”ヲ”, surface:”を”, known:true, allFeatures:”助詞,格助詞,一般,*,*,*,を,ヲ,ヲ”, conjugationType:”*”, partOfSpeechLevel2:”格助詞”, conjugationForm:”*”, allFeaturesArray:{”助詞”, “格助詞”, “一般”, “*”, “*”, “*”, “を”, “ヲ”, “ヲ”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”名詞”, baseForm:”予約”, pronunciation:”ヨヤク”, position:10, partOfSpeechLevel3:”*”, reading:”ヨヤク”, surface:”予約”, known:true, allFeatures:”名詞,サ変接続,*,*,*,*,予約,ヨヤク,ヨヤク”, conjugationType:”*”, partOfSpeechLevel2:”サ変接続”, conjugationForm:”*”, allFeaturesArray:{”名詞”, “サ変接続”, “*”, “*”, “*”, “*”, “予約”, “ヨヤク”, “ヨヤク”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”記号”, baseForm:”。”, pronunciation:”。”, position:12, partOfSpeechLevel3:”*”, reading:”。”, surface:”。”, known:true, allFeatures:”記号,句点,*,*,*,*,。,。,。”, conjugationType:”*”, partOfSpeechLevel2:”句点”, conjugationForm:”*”, allFeaturesArray:{”記号”, “句点”, “*”, “*”, “*”, “*”, “。”, “。”, “。”}, partOfSpeechLevel4:”*”}}, endTime:”1502971949537″, |log|:”", processTime:”0″}

japaneseTokenize.frameworkをmacOS 10.10以降用にビルドしたバイナリを用意しましたので、自己責任で~/Library/Frameworksフォルダに入れて使ってみてください。

→ Download japaneseTokenize framework Binary

AppleScript名:japaneseTokenizeのじっけん
– Created 2017-08-17 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “japaneseTokenize”
–https://github.com/murakami/workbook/tree/master/mac/Ruby
–http://d.hatena.ne.jp/shu223/20130318/1363566717

–http://piyocast.com/as/archives/4778

set targString to “来週の水曜日に会議を予約。” –”Make a meeting on next Wednesday.” in Japanese

set aRes to current application’s jTokenize’s parseString:targString
–> {{token:”来週”, romaji:”raishuu”, hurigana:”らいしゅう”}, {token:”の”, romaji:”no”, hurigana:”の”}, {token:”水曜”, romaji:”suiyou”, hurigana:”すいよう”}, {token:”日”, romaji:”hi”, hurigana:”ひ”}, {token:”に”, romaji:”ni”, hurigana:”に”}, {token:”会議”, romaji:”kaigi”, hurigana:”かいぎ”}, {token:”を”, romaji:”wo”, hurigana:”を”}, {token:”予約”, romaji:”yoyaku”, hurigana:”よやく”}, {token:”。”, romaji:”。”, hurigana:”。”}}

set aList to (aRes’s valueForKeyPath:“hurigana”) as list –ふりがな
–>  {”らいしゅう”, “の”, “すいよう”, “ひ”, “に”, “かいぎ”, “を”, “よやく”, “。”}

set bList to (aRes’s valueForKeyPath:“token”) as list –元のテキスト
–>  {”来週”, “の”, “水曜”, “日”, “に”, “会議”, “を”, “予約”, “。”}

set cList to (aRes’s valueForKeyPath:“romaji”) as list –hurigana (romaji)
–>  {”raishuu”, “no”, “suiyou”, “hi”, “ni”, “kaigi”, “wo”, “yoyaku”, “。”}

set bRes to current application’s jTokenize’s parseStringWithLinguisticTag:targString
set dList to (bRes’s valueForKeyPath:“scheme”) as list
–>  {”Noun”, “Particle”, “Noun”, “Noun”, “Particle”, “Noun”, “Particle”, “Noun”, “SentenceTerminator”}

return bRes as list
–> {{token:”来週”, |scheme|:”Noun”}, {token:”の”, |scheme|:”Particle”}, {token:”水曜”, |scheme|:”Noun”}, {token:”日”, |scheme|:”Noun”}, {token:”に”, |scheme|:”Particle”}, {token:”会議”, |scheme|:”Noun”}, {token:”を”, |scheme|:”Particle”}, {token:”予約”, |scheme|:”Noun”}, {token:”。”, |scheme|:”SentenceTerminator”}}

★Click Here to Open This Script 

2017/08/16 Markdown書類から見出し(Header)行を抽出_v2

指定のMarkdown書類から正規表現で見出し(Header)行を抽出し、見出しレベルと見出しテキストを出力するAppleScriptです。

指定フォルダ下のすべてのMarkdown書類をSpotlight処理で抽出し、各書類をこのScriptで処理。まとめて見出しレベルを統計処理してみました。複数の書籍で見出しレベルの分布などを比較するなど、割と重そうな処理をあっさり(数秒のオーダーで)処理できたのでよかったと思います。

AppleScript名:Markdown書類から見出し(Header)行を抽出_v2
– Created 2017-08-12 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4776

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

set aFile to choose file of type {“net.daringfireball.markdown”} –Markdown書類のUTI
set aStr to (read aFile as «class utf8»)
set aList to retHeaders(aStr) of me
–>  {{3, “choose file, choose folderで選んだ対象の名称変更”}, {3, “choose folderで選んだフォルダ内のファイルの名称変更”}, {3, “Finder上で選んだ(selection)ファイルの名称変更”}, {3, “POSIX pathのファイルの名称変更”}, {3, “ファイルを移動させたうえで名称変更”}, {3, “要注意事項(超重要、生死にかかわる)”}}

on retHeaders(aCon)
  set tList to {}
  
set regStr to “^#{1,6}[^#]*?$”
  
  
set headerList to my findPattern:regStr inString:aCon
  
repeat with i in headerList
    set j to contents of i
    
set regStr2 to “^#{1,6}[^#]*?”
    
set headerLevel to length of first item of (my findPattern:regStr2 inString:j)
    
set the end of tList to {headerLevel, text (headerLevel + 1) thru -1 in j}
  end repeat
  
  
return tList
end retHeaders

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 – so we can loop through
  
set theResult to {} – we will add to this
  
set theNSString to NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set theRange to (item i of theFinds)’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/08/13 ASOCからclass名を抽出してpropertyとして展開

Cocoaの機能を呼び出すASOCのscript自体を、Script Editorをコントロールして書き換えるAppleScriptです。Script Editorの最前面でオープン中のAppleScript書類(front document)の内容を読み取って書き換えを行い、新規AppleScript書類に出力します。

rewriteasoc1.gif

このところ、Blogへの掲載時にASOCのプログラムを手で書き換えていました。

  「current application’s NSString」

とプログラム中に書いておくと、プログラム1行が長くなり、Blog掲載時に読みにくくなってしまうためです。これを、

  「property NSString : a reference to current application’s NSString」

とプロパティ宣言してまとめていました(Script Debuggerのテンプレート風に)。

この作業が予想外にかったるいので、Script EditorをコントロールしてAppleScriptを書き換えるAppleScriptを書いてみました(こういうのをまとめたのがPiyomaru Script Assistantであります)。

AppleScriptを記述する「Script Editor」自体、AppleScriptでコントロール可能な「スクリプタブルな」アプリケーションの1つなので、AppleScriptからScript Editorをコントロールして編集中のAppleScript書類を加工する、という処理は日常茶飯事的に行なっています。そういう処理のうちの1つです。

それほど本気で書いていないので、正規表現での文字抽出などがかなりヌルい感じ(得意な人から見ると頭を抱えるレベル)ですが、ぼちぼち書きかえていけばよいでしょう。

また、試作品レベルなのでscript文によるscript objectの分割などはいっさい考慮していません。

置換除外リストの中には「実際に動かしてみたら正規表現によるピックアップが甘くて置換ターゲットにゴミが入っていた」ことへの小手先での対処データなどが冒頭に入りつつ、Objective-CではなくCの関数(property宣言できない)およびEnumの数値が大きすぎてAppleScriptの数値表現範囲を超えてしまい代入できないものなどを入れてあります。ただし、「とりあえず動けばいい」というレベルでしか書いていないので、完全なものではありません。

Script Editorでオープン中の最前面のAppleScriptを書き換えるようになっています。一番簡単なのはASObjC Explorer 4やScript Debuggerなどで本Scriptをオープンして実行する方法ですが、Script MenuやScript Editorのコンテクストメニューから呼び出すとよいでしょう。

AppleScript名:ASOCからclass名を抽出してpropertyとして展開
– Created 2017-08-12 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4774

set aCon to getFrontmostSEContents() of me
set aaList to paragraphs of aCon

set aList to retPropertiesClass(aCon) of me
set bList to retCurrentApplicationsClass(aCon) of me
set cList to {“\”", “\”,”, “NSMakeRange”, “NSMakePoint”, “NSMakeRect”, “NSUTF16BigEndianStringEncoding”, “NSUTF16LittleEndianStringEncoding”} –置換してはいけないリスト。text encoding系はenumの桁数が足りなくてpropertyに代入できない

set aSet to current application’s NSMutableSet’s setWithArray:aList –property宣言文の一覧
set bSet to current application’s NSMutableSet’s setWithArray:bList –script中のcurrent application文のクラス
set cSet to current application’s NSMutableSet’s setWithArray:cList

bSet’s minusSet:aSet –current applicationで指定したクラスで、property宣言していないものを計算
bSet’s minusSet:cSet –置換禁止リスト

set dList to bSet’s allObjects() as list

–Scriptの本文を書き換える
repeat with i in dList
  set j to contents of i
  
set tmpStr to “current application’s “ & j
  
set aCon to repChar(aCon, tmpStr, ” “ & j) of me
end repeat

–挿入部分のテキストを組み立てる
set repStr to return
repeat with i in dList
  set j to contents of i
  
set repStr to repStr & “property “ & j & ” : a reference to current application’s “ & j & return
end repeat
set repStr to repStr & return

set aInsPoint to getFirstInsertionPoint(aCon) of me

tell application “Script Editor”
  set aDoc to make new document
  
tell front document
    set contents to aCon
    
set selection to paragraph aInsPoint
    
set contents of selection to the repStr
    
    
try
      compile
    end try
  end tell
end tell

on getFirstInsertionPoint(aCon)
  set aList to paragraphs of aCon
  
set aCount to 1
  
repeat with i in aList
    set j to contents of i
    
if j = “” then
      exit repeat
    end if
    
set aCount to aCount + 1
  end repeat
  
  
return aCount
end getFirstInsertionPoint

on retPropertiesClass(aCon)
  set resList to {}
  
set regStr to “(property .*?:)”
  
set aRes to my findPattern:regStr inString:aCon capturing:1
  
  
repeat with i in aRes
    set j to first item of (paragraphs of i)
    
    
–正規表現もどきで取り出してみた(ぜんぜん改良の余地がある)
    
set j1 to repChar(j, “current application’s “, “”) of me
    
set j2 to repChar(j1, “property”, “”) of me
    
set j3 to repChar(j2, “:”, “”) of me
    
set j4 to repChar(j3, ” “, “”) of me
    
    
set the end of resList to j4
  end repeat
  
  
set res2List to uniquify1DList(resList, false) of me
  
return res2List
end retPropertiesClass

on retCurrentApplicationsClass(aCon)
  set resList to {}
  
set regStr to “(current application’s .*? )”
  
set aRes to my findPattern:regStr inString:aCon capturing:1
  
  
repeat with i in aRes
    set j to first item of (paragraphs of i)
    
    
–正規表現もどきで取り出してみた(ぜんぜん改良の余地がある)    
    
set j1 to repChar(j, “current application’s “, “”) of me
    
set j2 to repChar(j1, “’s “, “”) of me
    
set j3 to repChar(j2, “)”, “”) of me
    
set j4 to repChar(j3, “}”, “”) of me
    
set j5 to repChar(j4, “}”, “”) of me
    
set j6 to repChar(j5, ” “, “”) of me
    
    
set the end of resList to j6
  end repeat
  
  
set res2List to uniquify1DList(resList, false) of me
  
return res2List
end retCurrentApplicationsClass

on getFrontmostSEContents()
  tell application “Script Editor”
    set docCount to count every document
    
if docCount = 0 then error “No Document”
    
tell front document
      return contents
    end tell
  end tell
end getFrontmostSEContents

on replaceThis:thePattern inString:theString usingThis:theTemplate
  set theOptions to ((current application’s NSRegularExpressionDotMatchesLineSeparators) as integer) + ((current application’s NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to current application’s NSRegularExpression’s regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value)
  
set theResult to theRegEx’s stringByReplacingMatchesInString:theString options:0 range:{location:0, |length|:length of theString} withTemplate:theTemplate
  
return theResult as text
end replaceThis:inString:usingThis:

on findPattern:thePattern inString:theString
  set theOptions to ((current application’s NSRegularExpressionDotMatchesLineSeparators) as integer) + ((current application’s NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to current application’s 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 – so we can loop through
  
set theResult to {} – we will add to this
  
set theNSString to current application’s NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set theRange to (item i of theFinds)’s range()
    
set end of theResult to (theNSString’s substringWithRange:theRange) as string
  end repeat
  
return theResult
end findPattern:inString:

on findPattern:thePattern inString:theString capturing:n
  set theOptions to ((current application’s NSRegularExpressionDotMatchesLineSeparators) as integer) + ((current application’s NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to current application’s 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 – so we can loop through
  
set theResult to {} – we will add to this
  
set theNSString to current application’s NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set oneFind to (item i of theFinds)
    
if (oneFind’s numberOfRanges()) as integer < (n + 1) then
      set end of theResult to missing value
    else
      set theRange to (oneFind’s rangeAtIndex:n)
      
set end of theResult to (theNSString’s substringWithRange:theRange) as string
    end if
  end repeat
  
return theResult
end findPattern:inString:capturing:

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

–1D/2D Listをユニーク化
on uniquify1DList(theList as list, aBool as boolean)
  set aArray to current application’s NSArray’s arrayWithArray:theList
  
set bArray to aArray’s valueForKeyPath:“@distinctUnionOfObjects.self”
  
return bArray as list
end uniquify1DList

★Click Here to Open This Script 

AppleScript名:ASOCからclass名を抽出してpropertyとして展開(書き換え後)
– Created 2017-08-12 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4774

property NSRegularExpressionAnchorsMatchLines : a reference to current application’s NSRegularExpressionAnchorsMatchLines
property NSString : a reference to current application’s NSString
property NSArray : a reference to current application’s NSArray
property NSRegularExpressionDotMatchesLineSeparators : a reference to current application’s NSRegularExpressionDotMatchesLineSeparators
property NSMutableSet : a reference to current application’s NSMutableSet
property NSRegularExpression : a reference to current application’s NSRegularExpression

set aCon to getFrontmostSEContents() of me
set aaList to paragraphs of aCon

set aList to retPropertiesClass(aCon) of me
set bList to retCurrentApplicationsClass(aCon) of me
set cList to {“\”", “\”,”, “NSMakeRange”, “NSMakePoint”, “NSMakeRect”, “NSUTF16BigEndianStringEncoding”, “NSUTF16LittleEndianStringEncoding”} –置換してはいけないリスト。text encoding系はenumの桁数が足りなくてpropertyに代入できない

set aSet to NSMutableSet’s setWithArray:aList –property宣言文の一覧
set bSet to NSMutableSet’s setWithArray:bList –script中のcurrent application文のクラス
set cSet to NSMutableSet’s setWithArray:cList

bSet’s minusSet:aSet –current applicationで指定したクラスで、property宣言していないものを計算
bSet’s minusSet:cSet –置換禁止リスト

set dList to bSet’s allObjects() as list

–Scriptの本文を書き換える
repeat with i in dList
  set j to contents of i
  
set tmpStr to “current application’s “ & j
  
set aCon to repChar(aCon, tmpStr, ” “ & j) of me
end repeat

–挿入部分のテキストを組み立てる
set repStr to return
repeat with i in dList
  set j to contents of i
  
set repStr to repStr & “property “ & j & ” : a reference to current application’s “ & j & return
end repeat
set repStr to repStr & return

set aInsPoint to getFirstInsertionPoint(aCon) of me

tell application “Script Editor”
  set aDoc to make new document
  
tell front document
    set contents to aCon
    
set selection to paragraph aInsPoint
    
set contents of selection to the repStr
    
    
try
      compile
    end try
  end tell
end tell

on getFirstInsertionPoint(aCon)
  set aList to paragraphs of aCon
  
set aCount to 1
  
repeat with i in aList
    set j to contents of i
    
if j = “” then
      exit repeat
    end if
    
set aCount to aCount + 1
  end repeat
  
  
return aCount
end getFirstInsertionPoint

on retPropertiesClass(aCon)
  set resList to {}
  
set regStr to “(property .*?:)”
  
set aRes to my findPattern:regStr inString:aCon capturing:1
  
  
repeat with i in aRes
    set j to first item of (paragraphs of i)
    
    
–正規表現もどきで取り出してみた(ぜんぜん改良の余地がある)
    
set j1 to repChar(j, “current application’s “, “”) of me
    
set j2 to repChar(j1, “property”, “”) of me
    
set j3 to repChar(j2, “:”, “”) of me
    
set j4 to repChar(j3, ” “, “”) of me
    
    
set the end of resList to j4
  end repeat
  
  
set res2List to uniquify1DList(resList, false) of me
  
return res2List
end retPropertiesClass

on retCurrentApplicationsClass(aCon)
  set resList to {}
  
set regStr to “(current application’s .*? )”
  
set aRes to my findPattern:regStr inString:aCon capturing:1
  
  
repeat with i in aRes
    set j to first item of (paragraphs of i)
    
    
–正規表現もどきで取り出してみた(ぜんぜん改良の余地がある)    
    
set j1 to repChar(j, “current application’s “, “”) of me
    
set j2 to repChar(j1, “’s “, “”) of me
    
set j3 to repChar(j2, “)”, “”) of me
    
set j4 to repChar(j3, “}”, “”) of me
    
set j5 to repChar(j4, “}”, “”) of me
    
set j6 to repChar(j5, ” “, “”) of me
    
    
set the end of resList to j6
  end repeat
  
  
set res2List to uniquify1DList(resList, false) of me
  
return res2List
end retCurrentApplicationsClass

on getFrontmostSEContents()
  tell application “Script Editor”
    set docCount to count every document
    
if docCount = 0 then error “No Document”
    
tell front document
      return contents
    end tell
  end tell
end getFrontmostSEContents

on replaceThis:thePattern inString:theString usingThis:theTemplate
  set theOptions to ((NSRegularExpressionDotMatchesLineSeparators) as integer) + ((NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to NSRegularExpression’s regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value)
  
set theResult to theRegEx’s stringByReplacingMatchesInString:theString options:0 range:{location:0, |length|:length of theString} withTemplate:theTemplate
  
return theResult as text
end replaceThis:inString:usingThis:

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 – so we can loop through
  
set theResult to {} – we will add to this
  
set theNSString to NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set theRange to (item i of theFinds)’s range()
    
set end of theResult to (theNSString’s substringWithRange:theRange) as string
  end repeat
  
return theResult
end findPattern:inString:

on findPattern:thePattern inString:theString capturing:n
  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 – so we can loop through
  
set theResult to {} – we will add to this
  
set theNSString to NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set oneFind to (item i of theFinds)
    
if (oneFind’s numberOfRanges()) as integer < (n + 1) then
      set end of theResult to missing value
    else
      set theRange to (oneFind’s rangeAtIndex:n)
      
set end of theResult to (theNSString’s substringWithRange:theRange) as string
    end if
  end repeat
  
return theResult
end findPattern:inString:capturing:

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

–1D/2D Listをユニーク化
on uniquify1DList(theList as list, aBool as boolean)
  set aArray to NSArray’s arrayWithArray:theList
  
set bArray to aArray’s valueForKeyPath:“@distinctUnionOfObjects.self”
  
return bArray as list
end uniquify1DList

★Click Here to Open This Script 

2017/08/11 日本語形態素解析【新語対応】_ipadic_neologd(POST版)

apitoreのREST API「日本語形態素解析【Neologd対応】」のPOST対応版APIを呼び出すAppleScriptです。

apitore上の既存の形態素解析APIがPOST対応し、1MBまでのサイズのテキストの形態素解析が行えるようになったので、ためしに呼んでみることにしました。

ただし、テキストのサイズが大きくなった場合の処理時間が読めないので、いきなりMAXの1MBのテキストを形態素解析させるのは得策ではないでしょう(自分も様子見中)。

本Scriptをテストするためには、apitoreにサインアップしてAccess Tokenを取得して、Script末尾の伏字部分にコピー&ペーストしてください(掲載リストをそのまま実行してもエラーになります)。

個人的にはREST APIのAccess TokenをmacOSのKeychainに入れて、アカウント名とサイト名でKeychainに問い合わせる「keychain Lib」AppleScript Librariesを用いています。

AppleScript名:日本語形態素解析【新語対応】_ipadic_neologd(POST版)
– Created 2017-08-11 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.5″
use scripting additions
use framework “Foundation”
–use keychainLib : script “keychainLib”
–http://piyocast.com/as/archives/4773

property NSJSONSerialization : a reference to current application’s NSJSONSerialization
property NSMutableData : a reference to current application’s NSMutableData
property NSMutableURLRequest : a reference to current application’s NSMutableURLRequest
property |NSURL| : a reference to current application’s |NSURL|
property NSURLRequestReloadIgnoringLocalCacheData : a reference to current application’s NSURLRequestReloadIgnoringLocalCacheData
property NSURLConnection : a reference to current application’s NSURLConnection
property NSString : a reference to current application’s NSString
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSURLQueryItem : a reference to current application’s NSURLQueryItem
property NSURLComponents : a reference to current application’s NSURLComponents

(*)
tell current application
  set aTargStr to read (choose file) as «class utf8»–Read text as UTF-8
end tell
*)

–2017/7/3 Ver: POST対応。1MBまでのテキストを一気に形態素解析できるようになった
set aTargStr to “「ACE COMBAT INFINITY」3周年記念キャンペーンを実施。記念エンブレムをプレゼント”
set aTargList to paragraphs of aTargStr

set reqURLStr to “https://api.apitore.com/api/7/kuromoji-ipadic/tokenize”
set accessToken to retAccessToken() of me —Access Token
set aReq to {texts:aTargList}
set aRec to {access_token:accessToken}
set aURL to retURLwithParams(reqURLStr, aRec) of me

set aRes to callRestPOSTAPIAndParseResults(aURL, aReq) of me
set aRESCode to responseCode of aRes
set aRESHeader to responseHeader of aRes
set aRESTres to (json of aRes)
return aRESTres as record

–POST methodのREST APIを呼ぶ
on callRestPOSTAPIAndParseResults(aURL, aReq)
  set {theData, theError} to NSJSONSerialization’s dataWithJSONObject:aReq options:0 |error|:(reference)
  
if theData is missing value then error (theError’s localizedDescription() as text) number -10000
  
set postBody to NSMutableData’s |data|()
  
postBody’s appendData:theData
  
  
–Request
  
set aRequest to NSMutableURLRequest’s requestWithURL:(|NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“POST”
  
aRequest’s setCachePolicy:(NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:600
  
aRequest’s setValue:“application/json” forHTTPHeaderField:“Content-Type”
  
aRequest’s setHTTPBody:postBody
  
  
set aRes to NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to NSString’s alloc()’s initWithData:bRes encoding:(NSUTF8StringEncoding)
  
  
set jsonString to NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestPOSTAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to NSMutableDictionary’s dictionaryWithDictionary:aRec
  
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

on retAccessToken()
  return “XXXXxxxX-xxxx-XXXx-xxxX-XxxxXxXxxXXx” –API Tore Access Token
end retAccessToken

★Click Here to Open This Script 

2017/08/10 Jedit ΩのScript Menuの各種制限

Artman 21のマルチスタイルテキストエディタ「Jedit Ω」をためしてみたところ、Jedit Ω内蔵のScript Menuにはいろいろ制約があることが見えてきました。

AppleScriptは、実行環境が異なると一部の挙動が変わってくるため(Finderの選択ファイルの取得とか、セキュリティ面での制約とか)、さまざまな実行環境の傾向やクセをつかんでおくことは重要です。

Jedit Ω内蔵のScript Menu

さまざまな制約が存在しています。まず、現在のmacOSで標準的なバンドル形式のAppleScriptを実行できません。Script中にさまざまなリソースやフレームワークを入れて呼び出すようなことはできません。
→ Jedit Ωが、単純に拡張子「.scpt」のScriptのみ認識してバンドル形式の「.scptd」を認識しないために発生する問題のようです

Jedit ΩのScript Menu経由でオープン中の書類のクローズはできるものの、クローズ後もファイルのロックが継続された状態なのか、クローズ後に他のアプリケーションやScriptの機能で書き換えようとしてもできませんでした。実用上かなり制約が大きいため、このJedit Ωメニュー経由だと(現状では)何もできない感じです。

AppleScriptObjCのAppleScript(use framework “Foundation”を含む)の実行自体は行えるようです。ただし、前述のとおりバンドル形式では認識されません。このため、Script自体にFrameworkを同梱したり、AppleScript Librariesを同梱しても(この環境では)実行できません。

OSグローバルのScript Menu

メニューバーに常時表示され、最前面のアクティブなアプリケーションに合わせて該当するScriptが表示される仕組みです。

Jedit Ωに対してこのScript MenuからAppleScriptを実行して、オープン中のRTFをクローズして書き換え、変更して再オープンしてみたところ、とくに問題なく実行できました。また、当然バンドル形式のScriptの実行も問題ありません。

Script Editor

この環境ではとくに制約はありません。

2017/08/10 テキストのキーワード検索(結果をNSRangeのarrayで返す)

指定のテキストをキーワードで検索し、結果をNSRangeのarray(list)で返すAppleScriptです。

結果をNSRangeのarray(list)で返す必要があったのは、NSTextView中のスタイル付きテキストを検索して該当箇所の色を変更するような処理のために作ったためです。

33Kバイトのテキストファイルのキーワード検索に0.002秒程度かかりました(MacBook Pro Retina 2012 Core i7 2.66GHz)。

AppleScript名:テキストのキーワード検索(結果をNSRangeのlistで返す)
– Created 2017-08-09 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4771

property NSString : a reference to current application’s NSString
property NSMutableArray : a reference to current application’s NSMutableArray
property NSLiteralSearch : a reference to current application’s NSLiteralSearch

set aStr to “ATGC ACGT ATGC AGTC
ATGC ACGT ATGC AGTC
ATGC ACGT ATGC AGTC
ATGC ACGT ATGC AGTC

set aRes to searchWordRanges(aStr, “ATGC”) of me as list
–>  {{location:0, length:4}, {location:10, length:4}, {location:20, length:4}, {location:30, length:4}, {location:40, length:4}, {location:50, length:4}, {location:60, length:4}, {location:70, length:4}}

on searchWordRanges(aTargText as string, aSearchStr as string)
  set aStr to NSString’s stringWithString:aTargText
  
set bStr to NSString’s stringWithString:aSearchStr
  
  
set hitArray to NSMutableArray’s alloc()’s init()
  
set cNum to (aStr’s |length|()) as integer
  
  
set aRange to current application’s NSMakeRange(0, cNum)
  
  
repeat
    set detectedRange to aStr’s rangeOfString:bStr options:(NSLiteralSearch) range:aRange
    
if (detectedRange’s location) is equal to (current application’s NSNotFound) then exit repeat
    
    
hitArray’s addObject:detectedRange
    
    
set aNum to (detectedRange’s location) as integer
    
set bNum to (detectedRange’s |length|) as integer
    
    
set aRange to current application’s NSMakeRange(aNum + bNum, cNum - (aNum + bNum))
  end repeat
  
  
return hitArray
end searchWordRanges

★Click Here to Open This Script 

2017/08/09 NSStringからSHA-3のハッシュ値を求める

オープンソースの「NSString-SHA3」(By Jaeggerr)をFramework化した「SHA3Kit」を用いて、任意の文字列からSHA-3のハッシュ値を求めるAppleScriptです。

OS X 10.10以降で動作するようにSHA3Kit.frameworkのバイナリをビルドして用意しておきました。自己責任でframeworkを~/Library/Frameworksフォルダ以下にアーカイブを展開してコピーしたうえで本Scriptをおためしください。

–> Download Framework Binary

AppleScript名:NSStringからSHA-3のハッシュ値を求める
– Created 2017-08-09 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "SHA3Kit" –https://github.com/jaeggerr/NSString-SHA3
–http://piyocast.com/as/archives/4769

set origData to (current application’s NSString’s stringWithString:"hello")
set aHash1 to (origData’s sha3:256) as string
–> "1C8AFF950685C2ED4BC3174F3472287B56D9517B9C948127319A09A7A36DEAC8"

set aHash2 to (origData’s sha3:224) as string

set aHash3 to (origData’s sha3:384) as string

set aHash4 to (origData’s sha3:512) as string

★Click Here to Open This Script 

2017/08/09 AppleScript Release Notesが更新される(macOS 10.13 High Sierra)

AppleのWebをチェックしてみたら、macOS 10.13のAppleScript 2.7の変更点についても新たに追加されていたことを見つけました(昨日見たときには10.12への言及のみでした)。

macOS Version AppleScript JXA
10.10 2.4 1.0
10.11 2.5 1.1
10.12 2.5 1.1
10.13 2.7 1.1

10.13 Changes

macOS High Sierraでは、セキュリティに関する若干の改良や修正が行われた、とのこと。

・AppleScriptObjCで、method family deductionのための完全なルールを使用するようになった。このため、 +[NSCharacterSet newlineCharacterSet]のようなメソッドをクラッシュせずに呼べるようになった。

macOS 10.12上で問題になっている点といえば、AppleScriptドロップレットにファイルをドラッグ&ドロップしたときに、ファイルの拡張属性「com.apple.quarantine」の影響を受けて、ユーザーに安全確認を問い合わせていないファイルについてはドロップレット側でも処理しない、という問題。

また、これに付随してドロップレットにドラッグ&ドロップしたファイルの処理が複数回Openハンドラを呼び出されて実行される、ということが一番問題になっていました。

→ com.apple.quarantineがドロップレットに与える影響

同じ処理をmacOS 10.13 Public Betaで行なってみると、

 (1)ドロップレットにドロップしたファイルが複数回のopenハンドラ呼び出しに分割されて処理される状況は変わっていない

 (2)あきらかにヤバそうなファイル(未署名のアプリケーション。xattrコマンドで調べるとcom.apple.quarantine属性がついている)をドロップすると、(実行するわけでもないのに)起動したときと同じ警告ダイアログが出て処理が停止する

 (3)(2)のヤバいファイルをドロップした直後(いったん終了して)もう一度、安全なファイルをドロップしてもDock上でバウンドを繰り返す(起動中アニメーション)ものの、起動しないケースがある

といった様子です。

macOS 10.13 Public Betaでは、

・sayコマンドで日本語の特定の文字列(もげる、捥げる)を日本語音声で読み上げると一部の文字が抜ける(iOSでも同様)

のバグは未修正です。

2017/08/08 AppleScript Release Notesが更新される(macOS 10.12)

macOS 10.13のAppleScript 2.7のRelease Notesが掲載されていないかAppleのWebをチェックしてみたら、macOS 10.12上の変更点についての言及が新たに追加されていたことを見つけました。

macOS Version AppleScript JXA
10.10 2.4 1.0
10.11 2.5 1.1
10.12 2.5 1.1
10.13 2.7 1.1

10.12 Changes

macOS Sierraでは、パフォーマンスとセキュリティに関する改良や修正が行われた、とのこと。

・choose applicationコマンドの実行がScript EditorのAppleScript構文要素色分け設定をデフォルト設定状態に戻す、というバグを修正

2017/08/08 Metadata Lib 1.0

Spotlightの仕組みを用いてファイル検索を行うAppleScript Libraries「Metadata Lib」(By Shane Stanley)が公開されました。macOS 10.9以降の環境で利用できます。

Metadata Libのアーカイブをダウンロードしたのちに展開して、~/Library/Script Libraries/フォルダに「Metadata Lib.scptd」ファイルを入れればインストールは完了です。

とくにsdefでAppleScript用語辞書が定義されておらず、ソースもすべて読めるようになっています。ただし、各サブルーチンのハンドラの記法はAppleScriptネイティブの、

 someHander(aParamerer)

ではなく、Objective-C風(AppleScriptObjC風)の、

 someHander:aParameter

となっている点に注意が必要です。

内容に特殊なものはないので、「だいたいこんなかんじだよね〜」という軽いライブラリです。いきなり難解な内容にのたうちまわりながら苦しむといったことはまったくありません。今回のリリースはFirst Releaseですが、次あたりでAppleScript用語辞書をつけてきそうな雰囲気はあります。

mdls系コマンド

ファイルのメタデータを調べるにはシェルの「mdls」コマンドを使いますが、それと同じ内容を得られます。ただし、レコード中にオブジェクトを入れて返してくるので、mdfindよりも高速とのことです(Shane本人から念押し)。

use scripting additions
use mdLib : script “Metadata Lib” version “1.0.0″

set theFile to choose file
set theRecord to mdLib’s fetchMetadataFor:theFile

利用するだけれあれば、とくにAppleScriptObjCやCocoaの知識は必要とされません。

mdfind系コマンド

ファイルのメタデータ検索を行うにはシェルの「mdfind」コマンドを使いますが、だいたい同じです。本ライブラリで特徴的なのはNSPredicateで指定するPredicate文を記述して検索条件を指定する点にあります(自分はPredicate文は隠蔽するタチなので)。

また、mdfindよりも高速に検索が行える(Shane本人より念押し)ことに特徴があります。これは、mdfindがPOSIX Pathをテキストで返してくるのでAppleScript側で行単位にparseしつつPOSIX path経由でaliasに変換するような「データ変換」とshell呼び出しのオーバーヘッドの部分を指しているものと思われます(以前、mdfindとCocoa呼び出しで速度比較して、10%ぐらいCocoa呼び出しのほうが高速でした)。

そのため、本ライブラリでこの手のコマンドを利用するにはAppleScriptObjCやCocoaの知識が若干必要です。

もし、極力Cocoaの知識を使わずに本ライブラリによるメタデータ検索を行いたい場合には、あらかじめ検索パターンをFinder上で保存しておけば、searchStringsFromSavedSearch:ハンドラで保存しておいた検索パターンを指定して(Predicate文などを書かずに)、メタデータ検索を行うことも可能です(Shane本人より念押し)。

ひととおりライブラリの中身を見て回った感想は、Metadata検索系の機能だけでなく、ファイル検索系の他の機能も一緒にまとめてみては? というものでした。つまり、個人的にはぜんぜん使っていないFinder Tagとか、よく使っているFinder Labelによる検索とか、NSFileManagerによる再帰検索(Spotlightが効かない状態でも有効)など、バラバラに存在しているものを統合するといいんじゃないかと思えてきました。

どうなんでしょう?

2017/08/07 LocalのHTMLからJavaScriptを除去する

オープンソースのHTMLReader.framework(By Nolan Waite)を用いて、ローカルのHTML書類からJavaScriptの記述(タグ要素)を削除するAppleScriptです。

JavaScriptを削除しているのは単に処理サンプル掲載のためであり、実際にはJavaScriptにかぎらずタグ名で要素を指定できるようになっているので、とくにtitleだろうがtableだろうがcssだろうが、問題なく削除できます。

また、サンプル掲載のためにわざとHTMLのテキストエンコーディングを固定で呼び出していますが、こちらもAppleScript単独で日本語テキストのエンコーディング自動判別できるライブラリを整備してあるので、そちらを利用するとよいでしょう(実際にやっています)。

HTMLReader.frameworkについては、実際に使いきれないぐらいの機能が入っているので、いろいろ調べていますが、処理サンプルでいちばん参考にしているのはHTMLReader.framework自体のソースコードです。Objective-Cで書かれたソース自体を読みつつ、AppleScriptで呼び出しています。

本Script実行のためにはHTMLReader.frameworkをXcode上でビルドして、~/Library/Frameworksフォルダに入れて実行する必要があります。

本Scriptを実際に処理すると、与えたHTMLからJavaScript要素を削除したHTMLのテキストを返してきます。適宜、ファイルに保存するなりしてください。

HTMLに対して単に文字列処理して加工するのではなく、こうしたHTMLReaderのようなフレームワークを併用して高度な処理が行えるのはとても便利です。

AppleScript名:LocalのHTMLからJavaScriptを除去する
– Created 2017-08-04 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “HTMLReader” –https://github.com/nolanw/HTMLReader
–http://piyocast.com/as/archives/4765

property NSString : a reference to current application’s NSString
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSJapaneseEUCStringEncoding : a reference to current application’s NSJapaneseEUCStringEncoding
property HTMLDocument : a reference to current application’s HTMLDocument

set aPOSIX to POSIX path of (choose file)
set cRec to removeSpecifiedElementFromLocalHTML(aPOSIX, NSJapaneseEUCStringEncoding, “script”) of me

–指定されたローカルのHTMLファイルを、指定文字エンコーディングで読み込んで、指定されたタグ要素をすべて削除する
on removeSpecifiedElementFromLocalHTML(aPOSIX, anEncoding, aTag)
  set aPath to NSString’s stringWithString:aPOSIX
  
set aData to NSString’s stringWithContentsOfFile:aPath encoding:(anEncoding) |error|:(missing value)
  
if aData = missing value then return false
  
  
set aHTML to HTMLDocument’s documentWithString:aData
  
set elemList to (aHTML’s nodesMatchingSelector:aTag) as list
  
repeat with i in elemList
    i’s removeFromParentNode()
  end repeat
  
  
return aHTML’s serializedFragment() as string
end removeSpecifiedElementFromLocalHTML

★Click Here to Open This Script 

2017/08/06 Bluetoothに接続中のデバイスをメーカー名とマイナー種別で抽出

Bluetoothに接続中のデバイスをメーカー名とマイナー種別で抽出するAppleScriptです。

IOBluetooth経由ではなく、system_profiler経由でアクセスしてみました。メーカー名とデバイス種別で抽出できるので、自分がやりたい処理はできるようになったのですが、できればできたで衝撃の事実が発覚!

Apple製の周辺機器として流通しているものの中にも、メーカー名に”Apple”を返さないものがありました。事実、Magic Keyboard 2やMagic Mouse 2は製造元として「Broadcom」を返してきました。

仕様上、これでいいのか疑問が残る仕上がりです。ハードウェアについては十二分に満足していますが、こんなところでミソがつくとは、、、、

BroadcomはBluetoothの通信チップのメーカーであり、周辺機器そのものを製造しているわけではない、と認識していたのですが、気のせいでしょうか?(たぶん、ただしい)

参考までに、身の回りのBluetoothデバイスの種類のメジャー(Major)とマイナー(Minor)の名称をリストアップしておきます。詳細はユーティリティーフォルダ内の「システム情報」アプリケーションでご確認ください。

Magic Keyboard
Major: Peripheral, Minor:Keyboard

Magic Mouse 2
Major: Peripheral, Minor:Mouse

Mac mini
Major: Computer, Minor:Desktop

Bluetooth Speaker
Major: Audio, Minor:Loudspeaker

AirPods
Major: Audio, Minor:Headphones

iPad mini
Major: Computer, Minor:Handheld

iPhone
Major: Phone, Minor:Smartphone

本ルーチンについては、「意味なし予約語」を用いて英単語でそれっぽく表記できるようにしてみましたが、一般的なサブルーチンのハンドラの表記形式を用いても問題はありません。たまたまです。

AppleScript名:Bluetoothに接続中のデバイスをメーカー名とマイナー種別で抽出
– Created 2017-08-06 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4764

property NSArray : a reference to current application’s NSArray
property NSString : a reference to current application’s NSString
property NSMutableArray : a reference to current application’s NSMutableArray
property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property NSPropertyListSerialization : a reference to current application’s NSPropertyListSerialization
property NSPropertyListImmutable : a reference to current application’s NSPropertyListImmutable
property NSPropertyListFormat : a reference to current application’s NSPropertyListFormat
property NSPredicate : a reference to current application’s NSPredicate

–製造者が”Apple”のBluetoothデバイスをリストアップ
set qRes to returnBTPeripheral from “Apple”
–>  {{device_supportsESCO:”attrib_Yes”, device_role:”attrib_master”, device_manufacturer:”Apple (0×6, 0×03)”, device_services:”Handsfree, Wireless iAP, AVRCP Controller, Audio Sink, AVRCP Target, AAP Server”, device_isconnected:”attrib_Yes”, device_RSSI:-51, device_majorClassOfDevice_string:”Audio”, device_isconfigured:”attrib_Yes”, device_minorClassOfDevice_string:”Headphones”, device_interval:”441.25 ms”, device_addr:”XX-XX-XX-XX-XX-XX”, device_ConnectionMode:”attrib_sniff_mode”, device_productID:”0×2002″, device_supportsSSP:”attrib_Yes”, device_classOfDevice:”0×04 0×06 0×240418″, device_vendorID:”0×004C”, device_fw_version:”0×0372″, device_ispaired:”attrib_Yes”, device_supportsEDR:”attrib_Yes”}, {device_supportsESCO:”attrib_No”, device_manufacturer:”Apple (0×3, 0×31C)”, device_ispaired:”attrib_Yes”, device_services:”Apple Wireless Mouse”, device_isconnected:”attrib_No”, device_majorClassOfDevice_string:”Peripheral”, device_isNormallyConnectable:”attrib_Yes”, device_isconfigured:”attrib_Yes”, device_addr:”XX-XX-XX-XX-XX-XX”, device_productID:”0×030D”, device_supportsSSP:”attrib_No”, device_vendorID:”0×05AC”, device_classOfDevice:”0×05 0×20 0×2580″, device_minorClassOfDevice_string:”Mouse”, device_fw_version:”0×0084″, device_supportsEDR:”attrib_No”}}
–Appleのデバイスでも製造者がAppleになっていないものもある。Magic Keyboard 2とか

–種類(マイナー)が “Headphones”のBluetoothデバイスをリストアップ
set qRes to returnBTPeripheral about “Headphones”
–>  {{device_supportsESCO:”attrib_Yes”, device_role:”attrib_master”, device_manufacturer:”Apple (0×6, 0×03)”, device_services:”Handsfree, Wireless iAP, AVRCP Controller, Audio Sink, AVRCP Target, AAP Server”, device_isconnected:”attrib_Yes”, device_RSSI:-51, device_majorClassOfDevice_string:”Audio”, device_isconfigured:”attrib_Yes”, device_minorClassOfDevice_string:”Headphones”, device_interval:”441.25 ms”, device_addr:”XX-XX-XX-XX-XX-XX”, device_ConnectionMode:”attrib_sniff_mode”, device_productID:”0×2002″, device_supportsSSP:”attrib_Yes”, device_classOfDevice:”0×04 0×06 0×240418″, device_vendorID:”0×004C”, device_fw_version:”0×0372″, device_ispaired:”attrib_Yes”, device_supportsEDR:”attrib_Yes”}}

–製造者が”Apple”で、種類(マイナー)が “Headphones”のBluetoothデバイスをリストアップ
set qRes to returnBTPeripheral from “Apple” about “Headphones”
–> {{device_supportsESCO:”attrib_Yes”, device_role:”attrib_master”, device_manufacturer:”Apple (0×6, 0×03)”, device_services:”Handsfree, Wireless iAP, AVRCP Controller, Audio Sink, AVRCP Target, AAP Server”, device_isconnected:”attrib_Yes”, device_RSSI:-52, device_majorClassOfDevice_string:”Audio”, device_isconfigured:”attrib_Yes”, device_minorClassOfDevice_string:”Headphones”, device_interval:”441.25 ms”, device_addr:”XX-XX-XX-XX-XX-XX”, device_ConnectionMode:”attrib_sniff_mode”, device_productID:”0×2002″, device_supportsSSP:”attrib_Yes”, device_classOfDevice:”0×04 0×06 0×240418″, device_vendorID:”0×004C”, device_fw_version:”0×0372″, device_ispaired:”attrib_Yes”, device_supportsEDR:”attrib_Yes”}}

on returnBTPeripheral from devMaker as string : “” about kindName as string : “”
  set sRes to do shell script “/usr/sbin/system_profiler SPBluetoothDataType -detailLevel full -xml”
  
set aSource to (readPlistFromStr(sRes) of me) as list
  
set aaList to contents of first item of aSource
  
  
set resArray to NSMutableArray’s new()
  
  
set aList to _items of aaList
  
repeat with i in aList
    set aDict to (NSMutableDictionary’s dictionaryWithDictionary:(contents of i))
    
set aKeyList to (aDict’s allKeys()) as list
    
    
set dResList to (aDict’s valueForKeyPath:“device_title”)
    
repeat with ii in dResList
      set dKeyList to ii’s allKeys()
      
set dKey to first item of dKeyList
      
set dDic to (ii’s valueForKeyPath:dKey)
      
      
if devMaker is not equal to “” and kindName is not equal to “” then
        set qText to “device_manufacturer contains ’” & devMaker & “’ && device_minorClassOfDevice_string ==’” & kindName & “’”
      else if devMaker is not equal to “” then
        set qText to “device_manufacturer contains ’” & devMaker & “’”
      else if kindName is not equal to “” then
        set qText to “device_minorClassOfDevice_string ==’” & kindName & “’”
      end if
      
      
set dRes to filterRecListByLabel(dDic, qText) of me
      
if (dRes as list) is not equal to {} then
        (resArray’s addObject:(first item of dRes))
      end if
    end repeat
  end repeat
  
  
return resArray as list
end returnBTPeripheral

–stringのplistを読み込んでRecordに
on readPlistFromStr(theString)
  set aSource to NSString’s stringWithString:theString
  
set pListData to aSource’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aPlist to NSPropertyListSerialization’s propertyListFromData:pListData mutabilityOption:(NSPropertyListImmutable) |format|:(NSPropertyListFormat) errorDescription:(missing value)
  
return aPlist
end readPlistFromStr

–リストに入れたレコードを、指定の属性ラベルの値で抽出
on filterRecListByLabel(aRecList as list, aPredicate as string)
  set aArray to NSArray’s arrayWithArray:aRecList
  
  
set aPredicate to NSPredicate’s predicateWithFormat:aPredicate
  
set filteredArray to aArray’s filteredArrayUsingPredicate:aPredicate
  
  
set bList to filteredArray as list
  
return bList
end filterRecListByLabel

★Click Here to Open This Script 

2017/08/05 Bluetoothに接続中のデバイス名を取得、AirPodsを切断する

Bluetoothに接続中のデバイス情報を取得し、そのうち名称に「AirPods」を含むものの接続をオフ/オンにするAppleScriptです。

bluetooth1.png
▲実行前(AirPodsが接続状態)

bluetooth2.png
▲実行中(AirPodsが切断状態)

Bluetoothデバイスとの接続制御を行うAppleScriptをいろいろ試してみましたが、だいたい想定どおりのレベルのものができたような気がします。

本Scriptを実行すると、ペアリングずみのデバイス情報一覧を取得し、そのうち名称に「AirPods」を含むものを抽出してデバイスのアドレス情報を抽出し、接続を切断したのちに10秒時間待ちを行い、再度接続を行います。

この処理フロー全体には実用性も意味もありませんが、ひととおりの動作を検証するためのものです。実際にはこの処理フローの一部のみ(名称を取得するだけとか、指定名称のデバイスを接続するとか)を選択して使うことになります。

本来なら、デバイスのBluetoothアドレスからデバイス情報にアクセスして、デバイスタイプ(マウス、ヘッドセット、スピーカーなど)やメーカー名などで判定するような処理が理想的なんですが、一応妥協してデバイスの名称の文字列で判定するようにしたものです。

AppleScript名:Bluetoothに接続中のデバイス名を取得、AirPodsを切断する
– Created 2017-08-05 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.5″ –macOS 10.11 or later
use scripting additions
use framework “Foundation”
use framework “IOBluetooth”
–参照 https://github.com/lapfelix/BluetoothConnector
–http://piyocast.com/as/archives/4761

property IOBluetoothHostController : a reference to current application’s IOBluetoothHostController
property IOBluetoothDevice : a reference to current application’s IOBluetoothDevice

set dList to getActiveBluetoothDevices() of me
–>  {{deviceName:”Piyomaru AirPods”, deviceAddress:”Xx-XX-xX-Xx-xx-xx”}, {deviceName:”Takaaki Naganoya のマウス”, deviceAddress:”xx-xx-XX-xx-XX-Xx”}, {deviceName:”Takaaki Naganoya のキーボード #1″, deviceAddress:”XX-XX-xX-xx-Xx-xX”}}

set dRes to filterRecListByLabel(dList, “deviceName contains ’AirPods’”) of me
if dRes = {} then return false –Case: No match
set dAddr to dRes’s first item’s deviceAddress

set cnRes1 to disconnectBluetoothDeviceByAddress(dAddr) of me
–>  true (Successfully Disconnected)

delay 10

set cnRes2 to connectBluetoothDeviceByAddress(dAddr) of me
–>  true (Successfully Connected)

–指定アドレスのBluetooth Deviceを接続する
on connectBluetoothDeviceByAddress(addressStr as string)
  if getBluetoothPowerState() = false then error “Bluetooth Power is not active with your Mac”
  
set aBTList to IOBluetoothDevice’s pairedDevices() as list
  
  
repeat with i in aBTList
    set aClass to (current application’s NSStringFromClass(i’s |class|())) as string
    
if aClass is equal to “IOBluetoothDevice” then
      set anAddress to i’s addressString() as string
      
      
if anAddress = addressStr then
        i’s openConnection()
        
return true
      end if
    end if
  end repeat
  
return false
end connectBluetoothDeviceByAddress

–指定アドレスのBluetooth Deviceの接続を切る
on disconnectBluetoothDeviceByAddress(addressStr as string)
  if getBluetoothPowerState() = false then error “Bluetooth Power is not active with your Mac”
  
set aBTList to IOBluetoothDevice’s pairedDevices() as list
  
  
repeat with i in aBTList
    set aClass to (current application’s NSStringFromClass(i’s |class|())) as string
    
if aClass is equal to “IOBluetoothDevice” then
      set anAddress to i’s addressString() as string
      
      
if anAddress = addressStr then
        i’s closeConnection()
        
return true
      end if
    end if
  end repeat
  
return false
end disconnectBluetoothDeviceByAddress

–ペアリング済みのBluetooth Deviceの情報を取得
on getActiveBluetoothDevices()
  if getBluetoothPowerState() = false then error “Bluetooth Power is not active with your Mac”
  
set aBTList to IOBluetoothDevice’s pairedDevices() as list
  
set devList to {}
  
  
repeat with i in aBTList
    set aClass to (current application’s NSStringFromClass(i’s |class|())) as string
    
if aClass = “IOBluetoothDevice” then
      set aName to i’s |name|() as string
      
set anAddress to i’s addressString() as string
      
set aPaired to i’s isPaired() as boolean
      
–set aConnect to i’s isConnected() as boolean
      
if aPaired = true then
        set the end of devList to {deviceName:aName, deviceAddress:anAddress}
      end if
    end if
  end repeat
  
  
return devList
end getActiveBluetoothDevices

–Mac本体のBluetoothのパワー状態を取得
on getBluetoothPowerState()
  set aCon to IOBluetoothHostController’s alloc()’s init()
  
set pRes to (aCon’s powerState()) as boolean
end getBluetoothPowerState

–リストに入れたレコードを、指定の属性ラベルの値で抽出
on filterRecListByLabel(aRecList as list, aPredicate as string)
  set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aPredicate
  
set filteredArray to aArray’s filteredArrayUsingPredicate:aPredicate
  
  
set bList to filteredArray as list
  
return bList
end filterRecListByLabel

★Click Here to Open This Script 

2017/08/04 LocalのHTMLからMeta Keywordsとtitleを返す

LocalにダウンロードしたHTMLから検索用のMeta Keyとtitleを取得するAppleScriptです。

実行にはオープンソースの「HTMLReader.framework」(By Nolan Waite)を必要とします。

たまにWebサイトからcurlコマンドなどでHTMLをダウンロードして整理しておくことがあります。その際に、HTMLのファイル名が連番でとくに内容がよくわからないような場合に、ファイル名にHTMLのtitleを入れておき、HTML書類のコメントにHTML検索用キーワードを(HTMLから取得して)入れておくと整理しやすいことでしょう。

そのような用途のために作成したものです。実際には、1ファイルの情報を取得するものではなく、指定フォルダ以下のHTMLをすべて取得して順次ファイル名の変更や検索用タグづけを行うような処理を行います。

実行のためにはHTMLReader.frameworkをXcode上でビルドして、~/Library/Frameworksフォルダに入れて実行する必要があります。

ローカルにダウンロードしたHTMLということで、実ファイルを調べればテキストエンコーディングはわかるので決め打ちで指定していますが、別にAppleScriptだけでテキストエンコーディングの自動判別できるので、そのような機能を呼び出すようにしてもよいでしょう。

AppleScript名:LocalのHTMLからMeta Keywordsとtitleを返す
– Created 2017-08-04 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “HTMLReader” –https://github.com/nolanw/HTMLReader
–http://piyocast.com/as/archives/4760

property NSString : a reference to current application’s NSString
property NSJapaneseEUCStringEncoding : a reference to current application’s NSJapaneseEUCStringEncoding
property HTMLDocument : a reference to current application’s HTMLDocument

set aPOSIX to POSIX path of (choose file)
set aRec to retTitleAndMetaKeywordsFromLocalHTML(aPOSIX, NSJapaneseEUCStringEncoding) of me
–> {”title string”, “keywords strings”}

on retTitleAndMetaKeywordsFromLocalHTML(aPOSIX, anEncoding)
  set aPath to NSString’s stringWithString:aPOSIX
  
set aData to NSString’s stringWithContentsOfFile:aPath encoding:(anEncoding) |error|:(missing value)
  
if aData = missing value then return false
  
  
set aHTML to HTMLDocument’s documentWithString:aData
  
  
–HTML Title
  
set aTitleRes to ((aHTML’s nodesMatchingSelector:“title”)’s textContent’s firstObject()) as string
  
  
–Headers
  
set aHeaderRes to (aHTML’s nodesMatchingSelector:“head”)’s firstObject()
  
set headerElements to aHeaderRes’s children()’s array()
  
repeat with i in headerElements as list
    set j to contents of i
    
set aClass to j’s |class|()
    
set aKeys to “”
    
if aClass = current application’s HTMLElement then
      set aRec to j’s attributes()
      
set aName to (aRec’s valueForKey:“name”) as string
      
set aKeys to (aRec’s valueForKey:“content”) as string
      
if aName = “keywords” then
        exit repeat
      end if
    end if
  end repeat
  
  
return {aTitleRes, aKeys}
end retTitleAndMetaKeywordsFromLocalHTML

★Click Here to Open This Script 

2017/08/02 指定のアプリケーションのURL Schemeを取得する

指定のアプリケーションに設定してあるURL Schemeを取得するAppleScriptです。

指定のアプリケーションバンドル内のInfo.plistにあるCFBundleURLSchemesにアクセスして、カスタムURLプロトコルスキームを取得します。実行にはShane Stanleyの「Bridge Plus」AppleScriptライブラリを~/Library/Script Librariesフォルダにインストールしておくことが必要です。

たとえば、写真.app(Photos.app)であれば「photos://」というカスタムURLプロトコルが定義されており、本Scriptを実行して写真.app(Photos.app)を選択すると、

–> {appName:”Photos”, appBundleID:”com.apple.Photos”, urlScheme:{”photos”}}

という結果が返ってきます。

実際に調べてみると、OSのメジャーアップデート時に予想外のアプリケーションにカスタムURLスキームが新設されていることがあります。そういう調査のために作成したものです。

もちろん、1つのアプリケーションをいちいちchoose fileで選択して調べるとかそういうことではなく、指定フォルダ以下のアプリケーションをすべて取得して、それらのアプリケーションすべてのURL Schemeを調査するScriptのための部品です。手作業で1つ1つ調べるなんて、意味のないことです。

AppleScript名:指定のアプリケーションのURL Schemeを取得する
– Created 2017-07-23 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus”
–http://piyocast.com/as/archives/4759

load framework

set aP to choose file
set aURLrec to getAppURLSchemes(aP) of me
–> {appName:”Photos”, appBundleID:”com.apple.Photos”, urlScheme:{”photos”}}

on getAppURLSchemes(aP)
  set aURL to current application’s |NSURL|’s fileURLWithPath:(POSIX path of aP)
  
set aBundle to current application’s NSBundle’s bundleWithURL:aURL
  
set aDict to aBundle’s infoDictionary()
  
  
set appNameDat to (aDict’s valueForKey:“CFBundleName”) as string
  
set bundleIDat to (aDict’s valueForKey:“CFBundleIdentifier”) as string
  
  
set urlSchemes to (aDict’s valueForKey:“CFBundleURLTypes”)
  
if urlSchemes is not equal to missing value then
    set urlList to urlSchemes’s valueForKey:“CFBundleURLSchemes”
    
set urlListFlat to (current application’s SMSForder’s arrayByFlattening:urlList) as list
  else
    set urlListFlat to {}
  end if
  
  
set aRec to {appName:appNameDat, appBundleID:bundleIDat, urlScheme:urlListFlat}
  
return aRec
end getAppURLSchemes

★Click Here to Open This Script 

2017/08/01 JTHistogramでArrayのヒストグラムを計算

オープンソースの「JTHistogram」(By Kinokoo)をフレームワーク化したJTHistogramKitを呼び出して、NSArrayのヒストグラムを計算するAppleScriptです。

テスト用のランダム配列データの作成には、オープンソースの「objc-classes-dc-randomize」(By masakihirokawa)をフレームワーク化した「ArrayRandomize」を利用しています。

JTHistogramには該当要素数をかぞえる|histogram|()というメソッドと、構成比率を計算して求めるrelativeHistogram()というメソッドがあり、場合に応じて使い分けるとよいでしょう。

大量のデータのヒストグラム処理は割と見かけるものなので、このように高速に処理できる道具を揃えておくことには意義があります。

■乱数リスト作成+ヒストグラム(度数分布)計算 所要時間(@MacBook Pro Retina 2012 Core i7 2.66GHz, 8GB RAM)

 千項目(1,000 items):0.002 sec
 1万項目(10,000 items):0.019 sec
 10万項目(100,000 items):0.25 sec
 100万項目(1,000,000 items):3.6 sec
 1000万項目(10,000,000 items):41 sec

NSArrayの状態であれば(listに変換していない状態ならば)RAM 8GBのMacBook Proでも1000万項目の配列を扱えることに驚かされます。ASの常識的な10万項目程度なら0.25秒と非常に高速にヒストグラム計算が行えます。

ただし、得られたデータをそのままrecordに変換できないケースもあるので(ラベル値がAppleScriptのrecordで許容されないものだったりで)、値を取り出すにはCocoaのmethodを用いる必要があります。

1〜10万件程度の、AppleScriptにはやや手のかかる規模のデータの度数分布計算を行なっても、高速に処理できています(そりゃ、Objective-Cのプログラムを呼び出しているだけなので)。

JTHistogram.frameworkおよびArrayRandomize.frameworkをmacOS 10.10以降用にビルドしたバイナリを用意しましたので、自己責任で~/Library/Frameworksフォルダに入れて使って使ってみてください。

→ JTHistogramKit framework Binary

→ ArrayRandomize Framework Binary

AppleScript名:JTHistogramでArrayのヒストグラムを計算
– Created 2017-08-01 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “JTHistogramKit” –https://github.com/Kinokoo/JTHistogram
–http://piyocast.com/as/archives/4757

set anArray to current application’s NSMutableArray’s arrayWithArray:{1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3}
set histObj to current application’s JTHistogram’s alloc()’s initWithArray:anArray

set histDict to histObj’s |histogram|()
–>  (NSDictionary) {0:0, 3:4, 2:2, 1:6}

set relativeHistogramDict to histObj’s relativeHistogram()
–>  (NSDictionary) {0:0, 3:66.66667, 2:33.33334, 1:100}

★Click Here to Open This Script 

AppleScript名:JTHistogramでArrayのヒストグラムを計算(100,000項目)
– Created 2017-08-01 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ArrayRandomize” –https://github.com/masakihirokawa/objc-classes-dc-randomize
use framework “JTHistogramKit” –https://github.com/Kinokoo/JTHistogram
–http://piyocast.com/as/archives/4757

set anArray to (current application’s DCRandomize’s shuffle:1 max:999999)
set histObj to current application’s JTHistogram’s alloc()’s initWithArray:anArray

set histDict to histObj’s |histogram|()
–>  (NSDictionary) {0:0, 67423:1, 61298:1, 55173:1, 49048:1, 42923:1,….

★Click Here to Open This Script