2017/12/13 ハンドラの間接呼び出しのじっけん

ハンドラの間接呼び出しの実験を行うAppleScriptです。

variableeditor.gif

ハンドラの間接呼び出しは、Scripter誰もが一度は考えてみる課題ですが、自分にはどう考えてもできませんでした。もしかして、世界にひとりぐらいは実現している人間がいるかもしれませんが、とりあえず自分の結論は「無理」というものでした。

# recordを文字列に変換するのも、当初は「言語仕様的に不可能」と言われた空前絶後の超絶テクニックでしたが、いまでは割と手垢のついた「手口」に

ハンドラの存在確認が行える以上、間接的にハンドラを呼び出すことも不可能ではなさそうな雰囲気もしていますが、とりあえず行えていません。

結局、異なるAppleScript Librariesに同じ名前のハンドラを作っておき、呼び出し先のAppleScript Librariesを「代入」で切り替えることで、間接呼び出しっぽい処理を実現してみました。

scriptlibs.png

別にif文で条件分岐してもかまわなさそうな内容ですが、「やってできないことはない」という落とし所が見えました。

本Scriptでは、TextEdit.app、BBEdit、TextWranglermiJedit OmegaCotEditorという6本のテキストエディタをダイアログで選択し、当該エディタで花文字テキストの新規ドキュメントを作成しています。

–> Download This Script Bundle

AppleScript名:variableHandlerTest
– Created 2017-12-12 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use ed001 : script “textedit”
use ed002 : script “bbedit”
use ed003 : script “textwrangler”
use ed004 : script “mi”
use ed005 : script “jeditomega”
use ed006 : script “coteditor”
–http://piyocast.com/as/archives/5023

set aString to retFlowerText() of me

set appIDlist to {“com.apple.textedit”, “com.barebones.bbedit”, “com.barebones.textwrangler”, “net.mimikaki.mi”, “jp.co.artman21.JeditOmega”, “com.coteditor.CotEditor”}
set scriptObjList to {ed001, ed002, ed003, ed004, ed005, ed006}

set appNameList to {}
set appBundleIDList to {}

repeat with i in appIDlist
  set anAppID to contents of i
  
set aRes to checkAppInstallation(anAppID) of me
  
if aRes is not equal to false then
    set the end of appBundleIDList to anAppID
  end if
end repeat

set cRes to choose from list appBundleIDList
if cRes = false then return –Cancel

set appIDnum to retIndexNumInArray(appIDlist, contents of first item of cRes)
set objRef to contents of item appIDnum of scriptObjList

copy objRef to theTargetEditor –Very Important!!

tell theTargetEditor
  makeNewDocument given parameter:aString
end tell

on retFlowerText()
  return “                                            
           あああああ                            
           あああああ                            
           あああああ                            
           あああああ                            
    ああああああああああああああああああああああああああああ            
    ああああああああああああああああああああああああああああ            
    ああああああああああああああああああああああああああああ            
    ああああああああああああああああああああああああああああ            
           あああああ                            
           あああああ                            
           あああああ  ああああああ                    
           あああああああああああああああああ                
           あああああああああああああああああああ              
         あああああああああ   ああああああああああ             
        ああああああああ    ああああ  ああああああ            
       あああああああああ    ああああ   あああああ            
      あああああ ああああ   あああああ    あああああ           
     あああああ  あああああ  ああああ     あああああ           
     ああああ   あああああ あああああ     あああああ           
    あああああ   ああああああああああ      あああああ           
    ああああ     あああああああああ      あああああ           
   あああああ     ああああああああ       あああああ           
   あああああ     あああああああ        あああああ           
   あああああ     ああああああ        ああああああ           
   あああああ    ああああああ         ああああああ           
   ああああああああああああああ        あああああああ            
    ああああああああああああ       ああああああああ             
    あああああああああああ   あああああああああああああ             
     ああああああああ     あああああああああああ               
        あ         あああああああああ                 
                                            
                                            

end retFlowerText

–指定IDのアプリケーションがHDD上に存在するかを確認
on checkAppInstallation(appID)
  tell application “Finder”
    try
      set aRes to exists of application file id appID
    on error
      return false
    end try
  end tell
  
  
return true
end checkAppInstallation

–1D List中のシーケンシャルサーチ
on retIndexNumInArray(aList, aTarget)
  script obj
    property list : aList
  end script
  
  
set aCount to 1
  
set hitF to false
  
  
repeat with i in obj’s list
    set j to contents of i
    
if aTarget = j then
      return aCount
    end if
    
    
set aCount to aCount + 1
  end repeat
  
  
if hitF = false then return 0
end retIndexNumInArray

★Click Here to Open This Script 

AppleScript名:textedit
on makeNewDocument given parameter:aStr
  tell application id “com.apple.textedit”
    set aDoc to make new document
    
tell aDoc
      set text of it to aStr
    end tell
    
activate
  end tell
end makeNewDocument

★Click Here to Open This Script 

AppleScript名:bbedit
on makeNewDocument given parameter:aStr
  tell application id “com.barebones.bbedit”
    set aDoc to make new text document with properties {text:aStr}
    
activate
  end tell
end makeNewDocument

★Click Here to Open This Script 

AppleScript名:textwrangler
on makeNewDocument given parameter:aStr
  tell application id “com.barebones.textwrangler”
    set aDoc to make new text document with properties {text:aStr}
    
activate
  end tell
end makeNewDocument

★Click Here to Open This Script 

AppleScript名:mi
on makeNewDocument given parameter:aStr
  tell application id “net.mimikaki.mi”
    make new document with properties {content:aStr}
    
activate
  end tell
end makeNewDocument

★Click Here to Open This Script 

AppleScript名:jeditomega
on makeNewDocument given parameter:aStr
  tell application id “jp.co.artman21.JeditOmega”
    set aDoc to make new document with properties {text:aStr}
    
activate
  end tell
end makeNewDocument

★Click Here to Open This Script 

AppleScript名:coteditor
on makeNewDocument given parameter:aStr
  tell application id “com.coteditor.CotEditor”
    activate
    
set newDoc to make new document
    
tell newDoc
      set contents to aStr
    end tell
  end tell
end makeNewDocument

★Click Here to Open This Script 

2017/12/12 クリップボードに入ったproperty宣言部分を見た目の描画サイズ(幅)と文字コードでソート

クリップボードに入ったAppleScriptのproperty宣言部分を画面上の見た目と文字コードでソートしてクリップボードに入れるAppleScriptです。

以前に、同様の文字列長でソートするAppleScriptを作成したことがありましたが、

after2.png

文字列長できちんとソートしたものの、プロポーショナルフォントで表示されるため、

 「文字列長どおりにソートしても見た目がよくなるわけではない」

というたいへんに残念な結果に。

そこで、NSAttributedStringの描画サイズを取得するメソッドを利用して、画面上の見た目(画面上の描画サイズ)を取得し、描画サイズ(幅)と文字コード順でソートするようにしてみました。

sortbylooks.png

スクリプトエディタでCocoaオブジェクトへのproperty参照宣言部分をコピーし、本Scriptを呼び出し、ふたたびエディタ中に内容をペースとすると、画面上の描画サイズにもとづいて並べ替えたproperty宣言文が展開されます。

画面上の描画サイズを計算する際には、NSAttributedStringを組み立てる必要があるわけですが、この際の指定フォントについては最初にクリップボードに入っていた内容をNSAttributedStringとして取得し、その中で使われていた中で最も登場頻度の高いものを取得して自動で指定しています(やりすぎ)。

AppleScript名:クリップボードに入ったproperty宣言部分を見た目の描画サイズ(幅)と文字コードでソート
– Created 2017-12-08 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.5″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/5021

property NSFont : a reference to current application’s NSFont
property NSData : a reference to current application’s NSData
property NSColor : a reference to current application’s NSColor
property NSArray : a reference to current application’s NSArray
property NSString : a reference to current application’s NSString
property NSDictionary : a reference to current application’s NSDictionary
property NSPasteboard : a reference to current application’s NSPasteboard
property NSCountedSet : a reference to current application’s NSCountedSet
property NSMutableArray : a reference to current application’s NSMutableArray
property NSSortDescriptor : a reference to current application’s NSSortDescriptor
property NSAttributedString : a reference to current application’s NSAttributedString
property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSFontAttributeName : a reference to current application’s NSFontAttributeName
property NSKernAttributeName : a reference to current application’s NSKernAttributeName
property NSMutableParagraphStyle : a reference to current application’s NSMutableParagraphStyle
property NSLigatureAttributeName : a reference to current application’s NSLigatureAttributeName
property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString
property NSUnderlineStyleAttributeName : a reference to current application’s NSUnderlineStyleAttributeName
property NSParagraphStyleAttributeName : a reference to current application’s NSParagraphStyleAttributeName
property NSForegroundColorAttributeName : a reference to current application’s NSForegroundColorAttributeName

–クリップボードの内容を文字列として取得
set aStr to (the clipboard) as string
if aStr = “” then
  display dialog “No Data in Clipboard” buttons {“OK”} default button 1
  
return
end if

–クリップボードの内容をStyled Stringで取得して最頻出フォントを取得
set clipboardAttrStr to getClipboardASStyledText() of me
if clipboardAttrStr = missing value then
  display dialog “Can not get clipboard as Styled String” buttons {“OK”} default button 1
  
return
end if
set attrList to getAttributeRunsFromAttrString(clipboardAttrStr) of me
set anArray to (NSArray’s arrayWithArray:attrList)’s valueForKeyPath:“fontName”
set aFontList to (countItemsByItsAppearance(anArray) of me)
set aFontName to theName of first item of aFontList

–クリップボードから取得した文字データについて処理
set aList to paragraphs of aStr –行ごとにparseしてlist化
set bList to {}
repeat with i in aList
  set j to contents of i
  
if j ≠ {} then
    set jList to words of j
    
if jList ≠ {} then
      if contents of first item of jList = “property” then
        set curLabel to contents of second item of jList
        
        
–行をAttributed Stringとして組み立てて、画面描画時の仕上がりサイズを取得
        
set anAssrStr to makeRTFfromParameters(j, aFontName, 16, -2, 16) of me
        
set aSize to anAssrStr’s |size|() –画面描画時のサイズを取得
        
        
if class of aSize = record then
          set attrStrWidth to width of aSize
          
set attrStrHeight to height of aSize
        else if class of aSize = list then –macOS 10.13.xのバグ回避
          copy aSize to {attrStrWidth, attrStrHeight}
        end if
        
        
set the end of bList to {aLabel:curLabel, aCon:j, aWidth:attrStrWidth}
      end if
    end if
  end if
end repeat

if bList = {} then
  display dialog “Error” buttons {“OK”} default button 1
  
return
end if

–複数キーでソート(書式つきテキストの仕上がりサイズ幅、文字コード順でソート)
set aArray to NSArray’s arrayWithArray:bList
set desc1 to NSSortDescriptor’s sortDescriptorWithKey:“aWidth” ascending:true
set desc2 to NSSortDescriptor’s sortDescriptorWithKey:“aLabel” ascending:true selector:“localizedCaseInsensitiveCompare:”
set bArray to aArray’s sortedArrayUsingDescriptors:{desc1, desc2}

–ソートしたlist of recordからaCon(元のproperty宣言行そのもの)を一括で取り出す
set dArray to (NSMutableArray’s arrayWithArray:bArray)’s valueForKeyPath:“aCon”

–listをデリミタつきのテキストに
set dStr to retStrFromArrayWithDelimiter(dArray, return) of me

set the clipboard to (dStr & return)

–1D Listを文字列長でソート v2
on sort1DListByIndicatedStringLength(aList as list, aSortKey as string, sortOrder as boolean)
  set aArray to NSArray’s arrayWithArray:aList
  
set descLabel1 to NSString’s stringWithString:(aSortKey & “.length”)
  
set descLabel2 to NSString’s stringWithString:aSortKey
  
set desc1 to NSSortDescriptor’s sortDescriptorWithKey:descLabel1 ascending:sortOrder
  
set desc2 to NSSortDescriptor’s sortDescriptorWithKey:descLabel2 ascending:true selector:“localizedCaseInsensitiveCompare:”
  
set bArray to aArray’s sortedArrayUsingDescriptors:{desc1, desc2}
  
return bArray as list
end sort1DListByIndicatedStringLength

–リストを指定デリミタをはさんでテキスト化
on retStrFromArrayWithDelimiter(aList as list, aDelim as string)
  set anArray to NSArray’s arrayWithArray:aList
  
set aRes to anArray’s componentsJoinedByString:aDelim
  
return aRes as text
end retStrFromArrayWithDelimiter

–書式つきテキストを組み立てる
on makeRTFfromParameters(aStr as string, fontName as string, aFontSize as real, aKerning as real, aLineSpacing as real)
  set aVal1 to NSFont’s fontWithName:fontName |size|:aFontSize
  
set aKey1 to (NSFontAttributeName)
  
  
set aVal2 to NSColor’s blackColor()
  
set aKey2 to (NSForegroundColorAttributeName)
  
  
set aVal3 to aKerning
  
set akey3 to (NSKernAttributeName)
  
  
set aVal4 to 0
  
set akey4 to (NSUnderlineStyleAttributeName)
  
  
set aVal5 to 2 –all ligature ON
  
set akey5 to (NSLigatureAttributeName)
  
  
set aParagraphStyle to NSMutableParagraphStyle’s alloc()’s init()
  
aParagraphStyle’s setMinimumLineHeight:(aLineSpacing)
  
aParagraphStyle’s setMaximumLineHeight:(aLineSpacing)
  
set akey7 to (NSParagraphStyleAttributeName)
  
  
set keyList to {aKey1, aKey2, akey3, akey4, akey5, akey7}
  
set valList to {aVal1, aVal2, aVal3, aVal4, aVal5, aParagraphStyle}
  
set attrsDictionary to NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList
  
  
set attrStr to NSMutableAttributedString’s alloc()’s initWithString:aStr attributes:attrsDictionary
  
return attrStr
end makeRTFfromParameters

– クリップボードの内容をNSAttributedStringとして取り出して返す
on getClipboardASStyledText()
  set theNSPasteboard to NSPasteboard’s generalPasteboard()
  
set theAttributedStringNSArray to theNSPasteboard’s readObjectsForClasses:({NSAttributedString}) options:(missing value)
  
set theNSAttributedString to theAttributedStringNSArray’s objectAtIndex:0
  
return theNSAttributedString
end getClipboardASStyledText

–指定のNSAttributedStringから書式情報をlist of recordで取得
on getAttributeRunsFromAttrString(theStyledText)
  script aSpd
    property styleList : {}
  end script
  
  
set (styleList of aSpd) to {} —for output
  
  
set thePureString to theStyledText’s |string|() –pure string from theStyledText
  
  
set theLength to theStyledText’s |length|()
  
set startIndex to 0
  
  
repeat until (startIndex = theLength)
    set {theAtts, theRange} to theStyledText’s attributesAtIndex:startIndex longestEffectiveRange:(reference) inRange:{startIndex, theLength - startIndex}
    
    
set aText to (thePureString’s substringWithRange:theRange) as string
    
    
set aColor to (theAtts’s valueForKeyPath:“NSColor”)
    
if aColor is not equal to missing value then
      set aSpace to aColor’s colorSpace()
      
      
set aRed to (aColor’s redComponent()) * 255
      
set aGreen to (aColor’s greenComponent()) * 255
      
set aBlue to (aColor’s blueComponent()) * 255
      
      
set colList to {aRed as integer, aGreen as integer, aBlue as integer}
      
set colStrForFind to (aRed as integer as string) & ” “ & (aGreen as integer as string) & ” “ & (aBlue as integer as string)
    else
      set colList to {0, 0, 0}
      
set colStrForFind to “0 0 0″
    end if
    
    
set aFont to (theAtts’s valueForKeyPath:“NSFont”)
    
if aFont is not equal to missing value then
      set aDFontName to aFont’s displayName()
      
set aDFontSize to aFont’s pointSize()
    end if
    
    
set the end of (styleList of aSpd) to {stringVal:aText, colorStr:colStrForFind, colorVal:colList, fontName:aDFontName as string, fontSize:aDFontSize}
    
set startIndex to current application’s NSMaxRange(theRange)
  end repeat
  
  
return (styleList of aSpd)
end getAttributeRunsFromAttrString

–1D Listをアイテムの出現頻度順でソートして返す
on countItemsByItsAppearance(aList as list)
  set aSet to NSCountedSet’s alloc()’s initWithArray:aList
  
set bArray to NSMutableArray’s array()
  
set theEnumerator to aSet’s objectEnumerator()
  
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
bArray’s addObject:(NSDictionary’s dictionaryWithObjects:{aValue, (aSet’s countForObject:aValue)} forKeys:{“theName”, “numberOfTimes”})
  end repeat
  
  
set theDesc to NSSortDescriptor’s sortDescriptorWithKey:“numberOfTimes” ascending:false
  
bArray’s sortUsingDescriptors:{theDesc}
  
  
return bArray as list
end countItemsByItsAppearance

★Click Here to Open This Script 

2017/12/12 RTFの読み込み v2.2

指定のRTF(Ritch Text Format)ファイルを読み込んでNSAttributedStringに変換し、文字色、フォント名、フォントサイズ、文字列でrecordを作成して返すAppleScriptです。

任意のGUIアプリケーションの書式付きテキストから、指定の書式で該当箇所をピックアップするのはAppleScriptでは定番中の定番処理。テキストエディット.appのattribute runs(書式の一括取り出し)が有名です。もちろん、各種ワープロやDTPソフトウェアでも「出来てあたりまえ」の処理といえます(Pages、Numbers、Keynoteあたりはちょっと無理かも)。

Cocoa+Objective-Cの機能で同様の処理をいろいろ探してみましたが、NSAttributedStringからの書式による抽出はなかなか見つけられませんでした(不可能ではないものの、そういう処理にあまり関心がなさそうな雰囲気)。

そこで、MLで相談しつつNSAttributedStringからの文字書式(フォント名、ポイント数、文字色)による抽出が行えるように書いてみました。

ひたすらループ処理なので、処理データ量が増えるとそれなりに時間がかかります。ただし、いったん書式情報を抽出すれば、フィルタリング処理なども高速に行えます。

AppleScript名:RTFの読み込み v2.2
– Created 2017-12-10 by Takaaki Naganoya
– Modified 2017-12-11 by Shane Stanley
– Modified 2017-12-11 by Nigel Garvey
– Modified 2017-12-12 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/5020

property NSData : a reference to current application’s NSData
property NSString : a reference to current application’s NSString
property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString

set fRes to choose file of type {“public.rtf”}

set aFilePath to NSString’s stringWithString:(POSIX path of fRes)
set aData to NSData’s dataWithContentsOfFile:aFilePath options:0 |error|:(missing value)
set theStyledText to NSMutableAttributedString’s alloc()’s initWithData:aData options:(missing value) documentAttributes:(missing value) |error|:(missing value)

set attrRes to getAttributeRunsFromAttrString(theStyledText) of me
(*
{{stringVal:”abcdefg”, colorStr:”0 0 0″, colorVal:{0, 0, 0}, fontName:”Helvetica”, fontSize:12.0}, {stringVal:”12234534353534″, colorStr:”255 0 0″, colorVal:{255, 0, 0}, fontName:”Helvetica”, fontSize:12.0}, {stringVal:”dewhuiwheiuweriuwherwe”, colorStr:”0 0 0″, colorVal:{0, 0, 0}, fontName:”Helvetica”, fontSize:12.0}, {stringVal:”asda192837137129831″, colorStr:”255 0 0″, colorVal:{255, 0, 0}, fontName:”Helvetica”, fontSize:12.0}}
*)

on getAttributeRunsFromAttrString(theStyledText)
  script aSpd
    property styleList : {}
  end script
  
  
set (styleList of aSpd) to {} —for output
  
  
set thePureString to theStyledText’s |string|() –pure string from theStyledText
  
  
set theLength to theStyledText’s |length|()
  
set startIndex to 0
  
  
repeat until (startIndex = theLength)
    set {theAtts, theRange} to theStyledText’s attributesAtIndex:startIndex longestEffectiveRange:(reference) inRange:{startIndex, theLength - startIndex}
    
    
–String  
    
set aText to (thePureString’s substringWithRange:theRange) as string
    
    
–Color
    
set aColor to (theAtts’s valueForKeyPath:“NSColor”)
    
if aColor is not equal to missing value then
      set aSpace to aColor’s colorSpace()
      
      
set aRed to (aColor’s redComponent()) * 255
      
set aGreen to (aColor’s greenComponent()) * 255
      
set aBlue to (aColor’s blueComponent()) * 255
      
      
set colList to {aRed as integer, aGreen as integer, aBlue as integer} –for comparison
      
set colStrForFind to (aRed as integer as string) & ” “ & (aGreen as integer as string) & ” “ & (aBlue as integer as string) –for filtering
    else
      set colList to {0, 0, 0}
      
set colStrForFind to “0 0 0″
    end if
    
    
–Font
    
set aFont to (theAtts’s valueForKeyPath:“NSFont”)
    
if aFont is not equal to missing value then
      set aDFontName to aFont’s displayName()
      
set aDFontSize to aFont’s pointSize()
    end if
    
    
set the end of (styleList of aSpd) to {stringVal:aText, colorStr:colStrForFind, colorVal:colList, fontName:aDFontName as string, fontSize:aDFontSize}
    
set startIndex to current application’s NSMaxRange(theRange)
    
  end repeat
  
  
return (styleList of aSpd)
  
end getAttributeRunsFromAttrString

★Click Here to Open This Script 

2017/12/10 クリップボードに入ったproperty宣言部分を文字列長と文字コードでソート

クリップボードに入ったAppleScriptのproperty宣言部分を文字列長と文字コードでソートしてクリップボードに入れるAppleScriptです。

最近、AppleScript中でCocoaの機能を呼び出す処理を書く際に、プログラムリストが(横に)長くなることを避けるため、冒頭部分にpropertyで各Cocoa Objectへの参照を宣言するようにしていますが、、、、

正直、ここの宣言部分が乱雑なので、清書するために書いたものです。スクリプトメニューに入れて利用することを前提としています。

prop_before.png

property宣言部分をコピーして本Scriptを実行。property宣言部分の文字数が少ない順&文字コード順にソートを行います。

prop_after.png

結果をペースト。より、宣言部分が多いと(見た目の)効果が大きいです。

after2.png

対象がスクリプトエディタだけであれば、スクリプトエディタ自体をAppleScriptからコントロールして、冒頭のproperty宣言部分を抽出してproperty宣言文の構文要素をピックアップし、クリップボードを経由しないで書き換えることも可能です。

ただし、スクリプトエディタ以外の記述ソフト(Script Debugger、ASObjC Explorer 4、Xcode)を利用しているケースが多いことを考慮してクリップボード経由でデータのやり取りを行わせてみました。

AppleScript書類に対してのアクセスについては、正直Script Debuggerよりもスクリプトエディタの方が上(リッチテキスト形式でもアクセスできるので、構文色分けを利用して各種構文要素にアクセス可能)。スクリプト内容を解析して書き換えを行うような用途については、「Piyomaru Script Assistant」(電子書籍購入者の方への購入特典)をご利用いただいている方にはそのパワーをご理解いただけると思います。

同ツールはAppleScriptでAppleScriptを解析して、AppleScript自体を書き換えるというコンテクストメニューからのAppleScript呼び出しを超強化したものです。

AppleScript名:クリップボードに入ったproperty宣言部分を文字列長と文字コードでソート
– Created 2017-12-08 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/5014

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 NSSortDescriptor : a reference to current application’s NSSortDescriptor

set aStr to (the clipboard)
if aStr = “” then
  display dialog “No Data in Clipboard”
  
return
end if

set aList to paragraphs of aStr
set bList to {}
repeat with i in aList
  set j to contents of i
  
if j ≠ {} then
    set jList to words of j
    
if jList ≠ {} then
      if contents of first item of jList = “property” then
        set curLabel to contents of second item of jList
        
set the end of bList to {aLabel:curLabel, aCon:j}
      end if
    end if
  end if
end repeat

if bList = {} then
  display dialog “Error”
  
return
end if

–昇順ソート
set cList to sort1DListByIndicatedStringLength(bList, “aLabel”, true) of me

–ソートしたlist of recordからaCon(元のproperty宣言行そのもの)を一括で取り出す
set dArray to (NSMutableArray’s arrayWithArray:cList)’s valueForKeyPath:“aCon”

–listをテキストに
set dStr to retStrFromArrayWithDelimiter(dArray, return) of me

set the clipboard to dStr

–1D Listを文字列長でソート v2
on sort1DListByIndicatedStringLength(aList as list, aSortKey as string, sortOrder as boolean)
  set aArray to NSArray’s arrayWithArray:aList
  
set descLabel1 to NSString’s stringWithString:(aSortKey & “.length”)
  
set descLabel2 to NSString’s stringWithString:aSortKey
  
set desc1 to NSSortDescriptor’s sortDescriptorWithKey:descLabel1 ascending:sortOrder
  
set desc2 to NSSortDescriptor’s sortDescriptorWithKey:descLabel2 ascending:true selector:“localizedCaseInsensitiveCompare:”
  
set bArray to aArray’s sortedArrayUsingDescriptors:{desc1, desc2}
  
return bArray as list
end sort1DListByIndicatedStringLength

–リストを指定デリミタをはさんでテキスト化
on retStrFromArrayWithDelimiter(aList, aDelim)
  set anArray to NSArray’s arrayWithArray:aList
  
set aRes to anArray’s componentsJoinedByString:aDelim
  
return aRes as text
end retStrFromArrayWithDelimiter

★Click Here to Open This Script 

2017/12/08 指定文字の花文字を取得してRTFで書き出す

指定文字の花文字を取得して、収録グリフ数が10,000以上のフォントから30をデスクトップにRTFで書き出すAppleScriptです。

注意:ただし、本ScriptはRetina Display未対応です

hanamoji1.png

前のバージョンからの改良点は、

 〇慊衒源が指定フォント中にグリフを持っているかどうかチェックしてから処理
 ▲ぅ鵐好函璽襪気譴討い襯侫ント数が少ない場合への対処

といったところです。

花文字作成部分の処理内容は、

 ”漸荵慊衒源を収録しているフォントの一覧を取得
 ↓,納萋世靴織侫ントのうち、グリフ数が10,000以上のものをピックアップ
 スタイル付きテキストを作成。仕上がり(描画)サイズを取得
 せ転紊りサイズでRaw画像を作成
 ズ鄒したRaw画像を塗りつぶす(White)
 ε匹蠅弔屬靴Raw画像にスタイル付きテキストを描画
 Raw画像の各座標から色情報をピックアップ、指定スレッショルド値以上であればドットが存在していると判定
 ┘疋奪箸存在している場合には描画文字列を、存在していない場合にはスペースを配列に追加
 作成した2次元配列をテキストに変換。フォント名をテキストに含める指定を行なっている場合にはフォントのPostScript名を文字列に出力

というところです。

これまでは(macOS 10.10より前)AppleScriptで画像処理を行おうとすると、Photoshopあたりで処理するのが定番でしたが、Cocoaの機能を利用することで、カラープロファイル処理が厳密に求められるような内容でなければPhotoshopなしでけっこうな処理が行えるようになってきました。

テキスト処理において(文字コード自動判別が可能になったため)テキストエディタが要らなくなってきたように、データベース処理で(小規模データであれば)FileMaker Proが要らなくなってきて、同様に画像処理でもPhotoshopなしで処理できるものが増えてきた今日このごろです。

AppleScript名:指定文字の花文字を取得してRTFで書き出す
– Created 2017-11-19 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/5012

property NSArray : a reference to current application’s NSArray
property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSBezierPath : a reference to current application’s NSBezierPath
property NSMutableParagraphStyle : a reference to current application’s NSMutableParagraphStyle
property NSPNGFileType : a reference to current application’s NSPNGFileType
property NSFontAttributeName : a reference to current application’s NSFontAttributeName
property NSKernAttributeName : a reference to current application’s NSKernAttributeName
property NSLigatureAttributeName : a reference to current application’s NSLigatureAttributeName
property NSFont : a reference to current application’s NSFont
property NSUUID : a reference to current application’s NSUUID
property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString
property NSUnderlineStyleAttributeName : a reference to current application’s NSUnderlineStyleAttributeName
property NSImage : a reference to current application’s NSImage
property NSParagraphStyleAttributeName : a reference to current application’s NSParagraphStyleAttributeName
property NSString : a reference to current application’s NSString
property NSForegroundColorAttributeName : a reference to current application’s NSForegroundColorAttributeName
property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep
property NSColor : a reference to current application’s NSColor
property NSColorSpace : a reference to current application’s NSColorSpace
property NSFontManager : a reference to current application’s NSFontManager
property NSPredicate : a reference to current application’s NSPredicate
property NSDictionary : a reference to current application’s NSDictionary

set aString to “あ”
set hanaMax to 30
set hanaSize to 36
set targFontName to “Osaka-Mono” –”Courier New”–結果を出力するRTFのフォント名(PostScript名)

set fRes to getEveryFontPSNameANdGlyphsNum() of me
set theArray to NSArray’s arrayWithArray:fRes
set thePred to NSPredicate’s predicateWithFormat:“fontNum > 10000″
set bArray to (theArray’s filteredArrayUsingPredicate:thePred) as list

if hanaMax > (length of bArray) then
  set hanaMax to (length of bArray)
end if

set aCount to 1

repeat hanaMax * 2 times
  set aFontName to contents of item aCount of bArray
  
  
–花文字文字列を作成
  
set fRes to getHanamojiStr(hanaSize, fontName of aFontName, aString, 0.7, true) of me
  
if fResfalse then
    –StyledStringで結果出力(RTFとしてファイル保存)
    
set aStyledStr to makeRTFfromParameters(fRes, targFontName, 11, 0.0, 0.0) of me
    
set aRange to current application’s NSMakeRange(0, aStyledStr’s |length|())
    
set aVal1 to NSFont’s fontWithName:targFontName |size|:11
    
    
aStyledStr’s beginEditing()
    (
aStyledStr’s addAttribute:(NSFontAttributeName) value:aVal1 range:aRange)
    
aStyledStr’s endEditing()
    
    
set targFol to POSIX path of (path to desktop)
    
set aUUID to NSUUID’s UUID()’s UUIDString() as text
    
set bRes to my saveStyledTextAsRTF(aUUID, targFol, aStyledStr) –RTFで書き出す
    
    
set aCount to aCount + 1
    
if aCount > hanaMax then exit repeat
  end if
end repeat

–花文字文字列を計算して返す
on getHanamojiStr(aFontSize as real, aFontName as string, aString as string, aThread as real, incFontName as boolean)
  if length of aString is not equal to 1 then return false
  
  
–指定文字コードが指定フォント中に存在するかチェック
  
set fRes to retGlyphsInFont(aFontName, id of aString) of me
  
if fRes = false then return false
  
  
set aThreadShould to 768 * aThread
  
if (chkMultiByteChar(aString) of me) = false then
    set spaceChar to string id 12288 –全角スペース(UTF-16)
  else
    set spaceChar to string id 32 –半角スペース
  end if
  
  
set fillColor to NSColor’s whiteColor –塗り色
  
set bString to aString & ” “ –処理内容の帳尻合わせ(そのままだと右端が欠けるのでスペースを入れた)
  
set anAssrStr to makeRTFfromParameters(bString, aFontName, aFontSize, -2, (aFontSize * 1.2)) of me
  
set aSize to anAssrStr’s |size|()
  
  
if class of aSize = record then
    set attrStrWidth to width of aSize
    
set attrStrHeight to height of aSize
  else if class of aSize = list then –macOS 10.13.xのバグ回避
    copy aSize to {attrStrWidth, attrStrHeight}
  end if
  
  
set {xPos, yPos} to {0, 0}
  
  
set tmpImg1 to makeImageWithFilledColor(attrStrWidth, attrStrHeight, fillColor) of me
  
set tmpImg2 to drawAttributedStringsOnImage(tmpImg1, anAssrStr, xPos, yPos) of me
  
set aRawimg to NSBitmapImageRep’s imageRepWithData:(tmpImg2’s TIFFRepresentation())
  
  
–画像から順次指定座標の色データを拾って花文字listに反映
  
set strList to {}
  
repeat with y from 1 to attrStrHeight - 1
    
    
set strListX to {}
    
repeat with x from 0 to attrStrWidth - 1
      set tmpCol to getColorFromRawImage(aRawimg, x, y) of me
      
      
if tmpCol is not equal to false then
        if tmpCol is not equal to {255, 255, 255} then
          copy tmpCol to {tmpR, tmpG, tmpB}
          
if (tmpR + tmpG + tmpB) < aThreadShould then
            set the end of strListX to aString
          else
            set the end of strListX to spaceChar
          end if
        else
          set the end of strListX to spaceChar
        end if
      end if
      
    end repeat
    
set the end of strList to strListX
  end repeat
  
  
–2D List→Text
  
set aRes to list2dToStringByUsingDelimiters(strList, “”, return) of me
  
  
if incFontName = true then
    set fName to getDisplayedNameOfFont(aFontName) of me
    
set aRes to “■” & fName & return & return & aRes
  end if
  
  
return aRes
end getHanamojiStr

–指定Raw画像中の指定座標のピクセルの色をRGBで取り出す
on getColorFromRawImage(aRawimg, x as real, y as real)
  set origColor to (aRawimg’s colorAtX:x y:y)
  
set srgbColSpace to NSColorSpace’s deviceRGBColorSpace
  
if srgbColSpace = missing value then return false
  
  
set aColor to (origColor’s colorUsingColorSpace:srgbColSpace)
  
  
set aRed to (aColor’s redComponent()) * 255
  
set aGreen to (aColor’s greenComponent()) * 255
  
set aBlue to (aColor’s blueComponent()) * 255
  
  
return {aRed as integer, aGreen as integer, aBlue as integer}
end getColorFromRawImage

–画像のうえに指定のスタイル付きテキストを描画して画像を返す
on drawAttributedStringsOnImage(anImage, anAssrStr, xPos as real, yPos as real)
  anImage’s lockFocus()
  
anAssrStr’s drawAtPoint:(current application’s NSMakePoint(xPos, yPos))
  
anImage’s unlockFocus()
  
return anImage
end drawAttributedStringsOnImage

–書式つきテキストを組み立てる
on makeRTFfromParameters(aStr as string, fontName as string, aFontSize as real, aKerning as real, aLineSpacing as real)
  set aVal1 to NSFont’s fontWithName:fontName |size|:aFontSize
  
set aKey1 to (NSFontAttributeName)
  
  
set aVal2 to NSColor’s blackColor()
  
set aKey2 to (NSForegroundColorAttributeName)
  
  
set aVal3 to aKerning
  
set akey3 to (NSKernAttributeName)
  
  
set aVal4 to 0
  
set akey4 to (NSUnderlineStyleAttributeName)
  
  
set aVal5 to 2 –all ligature ON
  
set akey5 to (NSLigatureAttributeName)
  
  
set aParagraphStyle to NSMutableParagraphStyle’s alloc()’s init()
  
aParagraphStyle’s setMinimumLineHeight:(aLineSpacing)
  
aParagraphStyle’s setMaximumLineHeight:(aLineSpacing)
  
set akey7 to (NSParagraphStyleAttributeName)
  
  
set keyList to {aKey1, aKey2, akey3, akey4, akey5, akey7}
  
set valList to {aVal1, aVal2, aVal3, aVal4, aVal5, aParagraphStyle}
  
set attrsDictionary to NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList
  
  
set attrStr to NSMutableAttributedString’s alloc()’s initWithString:aStr attributes:attrsDictionary
  
return attrStr
end makeRTFfromParameters

–指定サイズの画像を作成し、背景を指定色で塗る
on makeImageWithFilledColor(aWidth as real, aHeight as real, fillColor)
  set anImage to NSImage’s alloc()’s initWithSize:(current application’s NSMakeSize(aWidth, aHeight))
  
  
anImage’s lockFocus()
  
set theRect to {{x:0, y:0}, {width:aWidth, height:aHeight}}
  
set theNSBezierPath to NSBezierPath’s bezierPath
  
theNSBezierPath’s appendBezierPathWithRect:theRect
  
fillColor’s |set|()
  
theNSBezierPath’s fill()
  
anImage’s unlockFocus()
  
  
return anImage
end makeImageWithFilledColor

–2D Listをアイテム間および行間のデリミタを個別に指定してテキスト変換
on list2dToStringByUsingDelimiters(aList as list, itemDelimiter, lineDelimiter)
  set outList to {}
  
repeat with i in aList
    set aStr to listToStringUsingTextItemDelimiter(i, itemDelimiter) of me
    
set the end of outList to aStr
  end repeat
  
  
set bStr to listToStringUsingTextItemDelimiter(outList, lineDelimiter) of me
  
return bStr
end list2dToStringByUsingDelimiters

on listToStringUsingTextItemDelimiter(sourceList as list, textItemDelimiter)
  set CocoaArray to NSArray’s arrayWithArray:sourceList
  
set CocoaString to CocoaArray’s componentsJoinedByString:textItemDelimiter
  
return (CocoaString as string)
end listToStringUsingTextItemDelimiter

–ユーザー環境にインストールされているすべてのフォントのPostScript名とグリフ数を返す
on getEveryFontPSNameANdGlyphsNum()
  set aFontList to NSFontManager’s sharedFontManager()’s availableFonts()
  
set thePred to NSPredicate’s predicateWithFormat:“NOT SELF BEGINSWITH ’.’”
  
set aFontList to (aFontList’s filteredArrayUsingPredicate:thePred) as list
  
  
set aList to {}
  
repeat with i in aFontList
    set aName to contents of i
    
set aNum to countNumberOfGlyphsInFont(aName) of me
    
set the end of aList to {fontName:aName, fontNum:aNum}
  end repeat
  
  
return aList
end getEveryFontPSNameANdGlyphsNum

–指定Postscript名称のフォントに定義されている文字数を数えて返す
on countNumberOfGlyphsInFont(fontName as string)
  set aFont to NSFont’s fontWithName:fontName |size|:9.0
  
if aFont = missing value then return false
  
set aProp to aFont’s numberOfGlyphs()
  
return aProp as number
end countNumberOfGlyphsInFont

–フォントのPostScript NameからDisplayed Nameを取得
on getDisplayedNameOfFont(aName as string)
  set aFont to NSFont’s fontWithName:aName |size|:9.0
  
set aDispName to (aFont’s displayName()) as string
  
return aDispName
end getDisplayedNameOfFont

–全角文字が存在するか
on chkMultiByteChar(checkString as string)
  set aStr to NSString’s stringWithString:checkString
  
set aRes to aStr’s canBeConvertedToEncoding:(current application’s NSASCIIStringEncoding)
  
return (aRes as boolean)
end chkMultiByteChar

–スタイル付きテキストを指定フォルダ(POSIX path)にRTFで書き出し
on saveStyledTextAsRTF(aFileName as string, targFol as string, aStyledString)
  set bstyledLength to aStyledString’s |string|()’s |length|()
  
set bDict to NSDictionary’s dictionaryWithObject:“NSRTFTextDocumentType” forKey:(current application’s NSDocumentTypeDocumentAttribute)
  
set bRTF to aStyledString’s RTFFromRange:(current application’s NSMakeRange(0, bstyledLength)) documentAttributes:bDict
  
  
set theName to NSString’s stringWithString:aFileName
  
set theName to theName’s stringByReplacingOccurrencesOfString:“/” withString:“_”
  
set theName to theName’s stringByReplacingOccurrencesOfString:“:” withString:“_”
  
set thePath to NSString’s stringWithString:targFol
  
set thePath to (thePath’s stringByAppendingPathComponent:theName)’s stringByAppendingPathExtension:“rtf”
  
  
return (bRTF’s writeToFile:thePath atomically:true) as boolean
end saveStyledTextAsRTF

–指定名称のフォントに指定の文字コードが含まれているかチェック
on retGlyphsInFont(fontName as string, strCode as integer)
  set aFont to NSFont’s fontWithName:fontName |size|:24.0
  
if aFont = missing value then return false
  
set aSet to aFont’s coveredCharacterSet()
  
set aRes to (aSet’s characterIsMember:strCode) as boolean
  
return aRes as list of string or string –as anything
end retGlyphsInFont

★Click Here to Open This Script 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

★Click Here to Open This Script 

2017/12/05 指定フォルダ内のラベル(Yellow, Red, Orange)のファイル一覧を取得

デスクトップフォルダ以下のファイルのうち、Spotlightで指定のラベル(複数のラベル)のファイルをリストアップするAppleScriptです。

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

本来、Spotlight検索時に指定するPredicate文では「IN」演算子(AppleScriptの「is in」とほぼ等価)が使えるので、当初書いていたような(↓)調子で書けるかと思っていたのですが、エラーが出て動きません。

Shaneにも相談してみたのですが、ORで複数条件を展開する方法しか通らないね、という話に。

であれば、文字列で決め打ちにするのではなく、Predicate文そのものをパラメータ(list)に合わせて生成するようにしてみました。

AppleScript名:指定フォルダ内のラベル(Yellow, Red, Orange)のファイル一覧を取得(未遂)
– Created 2017-09-23 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use mdLib : script “Metadata Lib” version “1.0.0″
–http://piyocast.com/as/archives/5010

set theFolder to path to desktop
set fRes to mdLib’s searchFolders:{theFolder} searchString:“kMDItemFSLabel IN %@” searchArgs:{{5, 6, 7}} –This script cause error
–> error “NSComparisonPredicate with type other than NSBetweenPredicateOperatorType and right expression which is an array given to NSMetadataQuery (kMDItemFSLabel IN {5, 6, 7})” number -10000

★Click Here to Open This Script 

AppleScript名:指定フォルダ内のラベル(Yellow, Red, Orange)のファイル一覧を取得
– Created 2017-12-05 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use mdLib : script “Metadata Lib” version “1.0.0″
–http://piyocast.com/as/archives/5010

set aLabelList to {5, 6, 7} –Yellow, Red, Orange
–0: No Label, 1: Gray, 2: Green, 3: Purple, 4: Blue, 5: Yellow, 6: Red, 7: Orange
set thePath to POSIX path of (path to desktop)
set aRes to spotlightFindByLabels(aLabelList, thePath) of me
–> list of POSIX path

on spotlightFindByLabels(aLabelList as list, thePath as string)
  set aList to makeRepeatinglList(length of aLabelList, “kMDItemFSLabel == %@”)
  
set aStr to retStrFromArrayWithDelimiter(aList, ” OR “) of me
  
set fRes to mdLib’s searchFolders:{thePath} searchString:aStr searchArgs:aLabelList
  
return fRes
end spotlightFindByLabels

–リストを指定デリミタをはさんでテキスト化
on retStrFromArrayWithDelimiter(aList as list, aDelim as string)
  set anArray to current application’s NSArray’s arrayWithArray:aList
  
return (anArray’s componentsJoinedByString:aDelim) as string
end retStrFromArrayWithDelimiter

–指定回数、指定アイテムを連結したリストを作成
on makeRepeatinglList(hitNum as integer, hitItem as string)
  set outList to {}
  
repeat hitNum times
    set the end of outList to hitItem
  end repeat
  
return outList
end makeRepeatinglList

★Click Here to Open This Script 

2017/12/04 自然言語で指定した日時以降に作成されたファイルをSpotlight検索

デスクトップフォルダ以下のファイルのうち、作成日付が自然言語で指定した日時以降のものをリストアップするAppleScriptです。

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

機能的にはとくに凝ったことをしていませんが、複数の機能を組み合わせることで柔軟な処理が行えるという見本です。ただし、OS側の自然言語ベースの日付特定処理がそれほど凝ったものではないので、ファイル検索を行う前にきちんと日時を取得できるかどうかのチェックが必要です。

AppleScript名:自然言語で指定した日時以降に作成されたファイルをSpotlight検索
– Created 2017-09-21 by Takaaki Naganoya
– Modified 2017-09-22 by Shane Stanley
– 2017 Piyomaru Software
use AppleScript version “2.5″
use scripting additions
use framework “Foundation”
use mdLib : script “Metadata Lib” version “1.0.0″
–http://piyocast.com/as/archives/5009

set aDate to getDatesIn(“先週の月曜日”) of me –”last Monday” in Japanese
log aDate

set thePath to POSIX path of (path to desktop)

set theFiles to mdLib’s searchFolders:{thePath} searchString:(“kMDItemFSCreationDate >= %@”) searchArgs:{aDate}
–> returns POSIX path list

on getDatesIn(aString)
  set anNSString to current application’s NSString’s stringWithString:aString
  
set theDetector to current application’s NSDataDetector’s dataDetectorWithTypes:(current application’s NSTextCheckingTypeDate) |error|:(missing value)
  
set theMatch to theDetector’s firstMatchInString:anNSString options:0 range:{0, anNSString’s |length|()}
  
if theMatch = missing value then error “No date found with String:” & aString
  
set theDate to theMatch’s |date|()
  
return theDate as date
end getDatesIn

★Click Here to Open This Script 

2017/12/02 デスクトップ上のRetina解像度のスクリーンショット画像でICNS以外のものを検索

デスクトップフォルダ以下のスクリーンショット画像ファイルのうち、Retina解像度(144dpi)の画像でICNS以外のものを検索するAppleScriptです。

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

さらに、スクリーンショット画像のうちフルスクリーンを対象にしたキャプチャなのか、部分的に選択されたエリアのキャプチャなのかを指定できます。また、カラープロファイルの指定もできるので、「複数接続されているディスプレイのうち、どのディスプレイ上で行われたキャプチャなのか」を特定することも可能です(すべてが同一モデルだと無理ですけれども)。

AppleScript名:デスクトップ上のRetina解像度のスクリーンショット画像でICNS以外のものを検索
– Created 2017-09-23 by Takaaki Naganoya
– Modified 2017-12-01 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use mdLib : script “Metadata Lib” version “1.0.0″
–http://piyocast.com/as/archives/5008

set theFolder to path to desktop
set fRes to mdLib’s searchFolders:{theFolder} searchString:“kMDItemResolutionHeightDPI == %@ && kMDItemResolutionWidthDPI == %@ && kMDItemIsScreenCapture == %@ && NOT (kMDItemContentTypeTree contains %@)” searchArgs:{144, 144, true, “com.apple.icns”}
–> {”/Users/me/Desktop/FromDesktop/スクリーンショット 2015-12-18 13.02.14.png”, “/Users/me/Desktop/FromDesktop/スクリーンショット 2015-08-14 18.08.41.png”}

★Click Here to Open This Script 

2017/12/01 デスクトップ上のRetina解像度の画像でICNS以外のものを検索

デスクトップフォルダ以下の画像ファイルのうち、Retina解像度の画像でICNS以外のものを検索するAppleScriptです。

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

AppleScript名:デスクトップ上のRetina解像度の画像でICNS以外のものを検索
– Created 2017-09-23 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use mdLib : script "Metadata Lib" version "1.0.0"
–http://piyocast.com/as/archives/5007

set theFolder to path to desktop
set fRes to mdLib’s searchFolders:{theFolder} searchString:"kMDItemResolutionHeightDPI == %@ && kMDItemResolutionWidthDPI == %@ && NOT (kMDItemContentTypeTree contains %@)" searchArgs:{144, 144, "com.apple.icns"}
–> {"/Users/me/Desktop/FromDesktop/スクリーンショット 2015-12-18 13.02.14.png", "/Users/me/Desktop/FromDesktop/conicon.png", "/Users/me/Desktop/FromDesktop/スクリーンショット 2015-08-14 18.08.41.png"}

★Click Here to Open This Script 

2017/12/01 Finderで選択中のフォルダ以下からPagesとMarkdown書類を抽出

Finder上で選択中のフォルダ(多分)の下からPages書類とMarkDown書類をすべてSpotlight検索するAppleScriptです。

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

AppleScript名:Finderで選択中のフォルダ以下からPagesとMarkdown書類を抽出
– Created 2017-10-28 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use mdLib : script “Metadata Lib” version “1.0.0″
–http://piyocast.com/as/archives/5006

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

set aResList to mdLib’s searchFolders:{origPath} searchString:(“kMDItemContentTypeTree CONTAINS %@ || kMDItemContentTypeTree CONTAINS %@”) searchArgs:{“net.daringfireball.markdown”, “com.apple.iwork.pages.sffpages”}

★Click Here to Open This Script 

2017/11/30 Keynoteで最前面の書類の全スライドにマスタースライドを再適用する

Keynoteでオープン中の最前面の書類の全スライドにマスタースライドを再適用するAppleScriptです。

書類中の各スライド(ページ)上のタイトルの大きさや位置が、作業中にバラバラになってしまった場合に、本Scriptを実行するとスライドを再適用するため、位置や大きさがそろいます。

本当は各スライド上のタイトルオブジェクト(default title item)の大きさや位置をループで揃えようとしてこの何倍かの長さのScriptを書いていたのですが、ふと「マスターを再適用すれば簡単にすぐ終わるのでは?」と思い、やってみたらあっという間でした。

確認はKeynote v7.3.1+macOS 10.12.6で行っています。macOS 10.13(「macOS Vista」と呼んでいる)は検証用のサブマシンには入れてありますが、あまりにバグが多くて出来が悪いので、メイン環境には入れないつもりです。

AppleScript名:Keynoteで最前面の書類の全スライドにマスタースライドを再適用する
–http://piyocast.com/as/archives/5005
tell application “Keynote”
  tell front document
    set sList to every slide
    
repeat with i in sList
      tell i
        set curSlide to base slide
        
set base slide to curSlide
      end tell
    end repeat
  end tell
end tell

★Click Here to Open This Script 

2017/11/29 指定のテキストからHTMLタグを除去(比較)

Shane Stanleyから「HTMLのタグ外す程度ならFrameworkまでビルドしなくてもいいんじゃね?」的な指摘&サンプルコード(NSXMLParser)があって、手元のさまざまなHTMLタグ外しルーチンを整理して検討してみました。

HTMLからのタグ外し(detag)については、いろいろやり方があって、それこそHTMLReaderみたいな「HTMLそのものを解釈する」フル装備のフレームワークもあれば、単にテキストとして処理するやり方までさまざまです。

昨日の件についていえば、「たまたま文字列中にHTMLタグのような文字列が入った場合に除去したい」というぐらいの用途でした。

また一方で、HTMLファイル全体を与えたときにどのような挙動になるのか、という評価軸もあります。

さらに、日本語文字列が入っていた場合にどうなるのか、文字化けせずに処理されるのかという話もあります。日本語圏で大丈夫ということは、だいたい同じぐらいの難易度の中国語、韓国語圏でも大丈夫であることが期待されます。アラビア語については未検証なのでわかりません。

そして、処理速度。0.00x秒ぐらいの処理時間でどちらが速いといっても、もはや時間が短すぎてよくわからないレベルに達しています。100回ループで回して時間計測すると、もはや誤差ぐらいの差でしかありません。

detag.png

評価結果を見てみると、

NSAttributedString:日本語が化けるし、<BR>タグが改行として解釈されるので、簡単ではあるものの使い勝手はいまひとつ

removeTagKit:高速で処理も安定している一方で、その程度でフレームワークを作ってインストールさせるのはどうか的な話があるのと、HTMLファイルを単体で与えたときに処理できませんでした

NSXMLParser:速くていいんだけど、<BR>とか<P>とかの単体で存在しているタグが混入しているとエラーが出たりした

NSScanner:HTMLを単なる文字列として処理するやり方がいちばん安定していた

といった、割と「身も蓋もない」話になってしまいました。

HTMLファイルを読み取ってテキスト抽出するのであれば、HTMLReader.frameworkを併用して文字コードを自動判別させてみるとよいでしょう。

AppleScript名:指定のテキストからHTMLタグを除去(NSXMLParser)
– Created 2017-11-29 by Shane Stanley
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions
–http://piyocast.com/as/archives/5004

property textArray : missing value
property anError : missing value

set theString to "<a>repeat</a>〜end repeat"
my stripHTMLTagsIn:theString
–>  "repeat〜end repeat"

on stripHTMLTagsIn:theString
  set my textArray to current application’s NSMutableArray’s array()
  
set theString to current application’s NSString’s stringWithFormat_("<root>%@</root>", theString)
  
set theString to theString’s stringByReplacingOccurrencesOfString:"&" withString:"&amp;"
  
set theData to theString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set theParser to current application’s NSXMLParser’s alloc()’s initWithData:theData
  
theParser’s setDelegate:me
  
set theResult to theParser’s parse()
  
if not theResult then error (anError’s |description|() as text)
  
return (textArray’s componentsJoinedByString:"") as list of string or string –as anything
end stripHTMLTagsIn:

on parser:theParser foundCharacters:aString
  textArray’s addObject:aString
end parser:foundCharacters:

on parser:anNSXMLParser parseErrorOccurred:anNSError
  set my anError to anNSError
end parser:parseErrorOccurred:

★Click Here to Open This Script 

AppleScript名:指定のテキストからHTMLタグを除去(NSScanner)
– Created 2016-12-12 by Shane Stanley
– Modified 2016-12-14 by edama2
– Modified 2017-11-28 by Takaaki Naganoya
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/5004

–set aStr to read (choose file)
set aStr to "<a>repeat</a>〜end repeat<BR>"
set aRes to (trimStrFromTo(aStr, "<", ">") of me)
–>  "repeat〜end repeat"

on trimStrFromTo(aParamStr, fromStr, toStr)
  set theScanner to current application’s NSScanner’s scannerWithString:aParamStr
  
set anArray to current application’s NSMutableArray’s array()
  
  
repeat until (theScanner’s isAtEnd as boolean)
    set {theResult, theKey} to theScanner’s scanUpToString:fromStr intoString:(reference)
    
    
theScanner’s scanString:fromStr intoString:(missing value)
    
set {theResult, theValue} to theScanner’s scanUpToString:toStr intoString:(reference)
    
if theValue is missing value then set theValue to ""
    
    
theScanner’s scanString:toStr intoString:(missing value)
    
    
anArray’s addObject:theValue
  end repeat
  
  
if anArray’s |count|() = 0 then return aParamStr
  
  
copy aParamStr to curStr
  
repeat with i in (anArray as list)
    set curStr to repChar(curStr, fromStr & i & toStr, "") of me
  end repeat
  
  
return curStr
end trimStrFromTo

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

★Click Here to Open This Script 

2017/11/28 指定のテキストからHTMLタグを除去

オープンソースの「NSString_striphtml」(By Leigh McCulloch)をFramework化したremoveTagKitを呼び出して指定文字列からHTMLタグを除去するAppleScriptです。

昔からこの種類のルーチンは存在していましたが、ちょっと高機能なものも欲しくなってきた今日このごろ。

いろいろ検討したところ、NSAttributedString経由でHTMLを解釈してテキストを取得する方法が手っ取り早くて(すでに組んであったので)いい感じでしたが、日本語の文字列が入ると文字化けが発生。

こちらのNSString_striphtmlのほうが18倍ぐらい高速で、改行タグが入っても改行として解釈されない点が自分の用途にかなっていました。

OS X 10.10以降用にビルドしたFrameworkのバイナリを用意したため、各自自己責任でFrameworkを~/Library/Frameworksフォルダに入れておためしください。

–> Download Framework Binary

AppleScript名:指定のテキストからHTMLタグを除去(removeTagKit)
– Created 2017-11-25 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “removeTagKit” –https://gist.github.com/leighmcculloch/1202238#file-nsstring_striphtml-m
–http://piyocast.com/as/archives/5002

set aStr to “<br />repeat〜end repeat”
set cStr to (current application’s NSString’s stringWithString:aStr)’s stripHtml() as string – under 0.001sec
–>  ”repeat〜end repeat”

★Click Here to Open This Script 

AppleScript名:指定のテキストからHTMLタグを除去(NSAttributedString)
– Created 2017-11-25 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/5002

set aStr to “<br />repeat〜end repeat”
set bStr to decodeHTMLasString(aStr) of me –0.004sec
(*

repeat
〜end repeat”
*)

on decodeHTMLasString(aStr)
  set anNSString to current application’s NSString’s stringWithString:aStr
  
set theData to anNSString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set styledString to current application’s NSAttributedString’s alloc()’s initWithHTML:theData documentAttributes:(missing value)
  
set plainText to (styledString’s |string|()) as string
  
return plainText
end decodeHTMLasString

★Click Here to Open This Script 

2017/11/28 2Dリストから、指定カラムのデータを削除する v3

2D List(配列変数)から、指定カラムのデータを削除するAppleScriptです。

Pure AppleScriptの機能の範囲でも、Cocoaの機能の範囲でも、結局は行ごとにループを回さないといけないようなので、もうちょっとクールな(ループなしで削除するような)解決策もありそうですが、どんなものでしょう。

AppleScript名:2Dリストから、指定カラムのデータを削除する v3
– Created 2017-11-25 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/5000

set aList to {{1, 2, 3}, {2, 3, 4}, {3, 4, 5}}
set removeIndex to 3 –1 based index
set bList to removeColumnFrom2DArray(aList, removeIndex) of me
–>  {{1, 2}, {2, 3}, {3, 4}}

on removeColumnFrom2DArray(aList as list, removeIndex as integer)
  set newArray to {}
  
set realIndex to removeIndex - 1 –index conversion from 1-based to 0-based
  
repeat with i in aList
    set anArray to (current application’s NSMutableArray’s arrayWithArray:i)
    (
anArray’s removeObjectAtIndex:realIndex)
    
set the end of newArray to anArray as list of string or string –as anything
  end repeat
  
return newArray
end removeColumnFrom2DArray

★Click Here to Open This Script 

2017/11/25 applescript-stdlibに入っていた未知の機能(2)

記述内容に癖があって(技術的な難易度が高くて)解析しにくいapplescript-stdlib。実用性はいまひとつですが、技術を誇示するためのショーケースとしては古今例を見ないほど。その技術的な側面にスポットライトを当て、利用可能な要素技術をピックアップする記事の続編です。

script objectのネスティング

AppleScriptObjCのScriptにおいて、script文でその内部に別のAppleScriptObjCのScript Objectを定義することは、AppleScriptの処理系によって許可されていません(執筆時点での確認事項。Shane Stanleyとの間で事実確認ずみ)。

ただ、propertyへの高速アクセスを目的として、AppleScriptObjCのScript中にPure AppleScriptのScript Objectを宣言することはできていました。

applescript-stdlib中ではscript objectのネスティングなどが多用されており、その中には、Pure AppleScriptのScript中にAppleScriptObjCのscript objectを宣言して呼び出す、という使われ方がされていました。

たしかに、このやり方は試していなかったので、何かのときに利用できるかもしれません。

–Pure AppleScript
use AppleScript version “2.4″
use scripting additions

set aRes to testRun() of me

on testRun()
  
  script aScript
    —AppleScriptObjC
    
use framework “Foundation”
    
    on test2(aStr)
      set aProp to version of AppleScript
      
log aProp
      
set bStr to (reverse of characters of aStr) as string
      
return (current application’s NSString’s stringWithString:bStr) as string
    end test2
    
  end script
  
  —Pure AppleScript  
  
set bRes to aScript’s test2(“ABC”)
  
end testRun

★Click Here to Open This Script 

script objectをdelegateに指定

CocoaのGUI部品の中には、NSTableViewのようにGUI部品上でのイベント発生を指定したdelegateで受信する機能を持つものが多くあります。また、その際にはハンドラ名称を指定できるタイプのもの(NSTimerとか)もあれば、GUI部品側が指定した固定のハンドラを呼び出すものもあります。

ここでは、後者を対象として話をしますが・・・そのdelegate methodをscript文で論理分割した先のハンドラで受信する、という記述が行われていました。script文は使うと便利(巨大Script同士の結合とか)ですが、多用しすぎると問題を起こしやすそうな疑念があって(あと、見た目に難しそうに見えてメンテナンス時の心理的な障壁が上がってしまいそうな)、あまり使わないようにしてきました。

set responseBodyData to current application’s NSMutableData’s |data|()

script sessionTaskDelegate
  on URLSession:asocSession dataTask:asocTask didReceiveData:asocData
    responseBodyData’s appendData:asocData
  end URLSession:dataTask:didReceiveData:
end script

set asocSession to current application’s NSURLSession’s sessionWithConfiguration:sessionConfig delegate:sessionTaskDelegate delegateQueue:(missing value)

★Click Here to Open This Script 

handlerの存在確認

指定のScript Object中に指定名称のハンドラ(サブルーチン)が存在しているかどうかの確認機能が入っていました。無条件にリストアップが行えるわけではなく、指定名称のハンドラの存在確認ができるだけのようです。

「handler」なんていう予約語が存在していたことにビックリです。

set aRef to a reference to test1 of me
set aRes to hasHandler(aRef) of me
–> true

set bRef to a reference to test2 of me
set bRes to hasHandler(bRef) of me
–> false

on test1()
  
end test1

on hasHandler(handlerRef)
  try
    handlerRef as handler
    
return true
  on error number -1700
    return false
  end try
end hasHandler

★Click Here to Open This Script 

set aRef to a reference to test1_withData_ of me
set aRes to hasHandler(aRef) of me
–> true

set bRef to a reference to test0 of me
set bRes to hasHandler(bRef) of me
–>  true

–Objective-Cっぽいハンドラ
on test1:aParam withData:bParam
  
end test1:withData:

–無意味句を使った英文っぽいハンドラ
on test0 for aInt at bInt
  return (aInt + bInt)
end test0

on hasHandler(handlerRef)
  try
    handlerRef as handler
    
return true
  on error number -1700
    return false
  end try
end hasHandler

★Click Here to Open This Script 

実際、このhasHandlerによるハンドラの存在確認にはいろいろと制約条件があるようで、下記のようにさまざまな注意事項が書かれていました。また、ドキュメント化されていない仕様なので、トラブル源になる(OSのアップデート時にAppleにバグを作られる)可能性についても指摘があります。

– CAUTION: `hasHander` relies on AS handlers’ partial ability to behave as AS objects in that they can be retrieved by name, assigned to variables, and coerced to `handler` type. Be aware, however, this object-like behavior is undocumented and essentially undefined: AS handlers are not closures, so moving them around will completely break their lexical and dynamic bindings, causing seriously bizarre and incorrect behavior if subsequently called. The only reason `hasHandler` resorts to such hackery because AS lacks the introspection/stack trace capabilites to do the job right (either by asking the containing script object to describe its contents, or by calling the handler speculatively then examining the stack trace to determine if error -1708 was due to the handler not existing or a bug occurring within it).

– CAUTION: `hasHandler` only works for handlers with identifier-based names; do not use to check for existence of handlers with keyword-based names as that will result in incorrect behavior.

2017/11/24 applescript-stdlibに入っていた未知の機能(1)

applescript-stdlibは、記述内容に癖があって(技術的な難易度が高くて)解析しにくいので、とてもメンテナンスできない代物でしたが、部分的に解析してみると「宇宙人の乗ってきた宇宙船の部品」みたいな未知の機能がいろいろ入っていました。

NSObjectの判定

変数に入っている内容がCocoaのオブジェクト、総称するとNSObjectであるかどうかの判定機能が入っていました。

一般的に、Pure AppleScriptのオブジェクトであれば「class of 変数」で変数内容のクラス(型)を取得できますし、Cocoaのオブジェクトでもクラス名を取得することは可能です

ただ、事前にどちらの世界のオブジェクトなのかがわからないと対処のしようがありません。変数の中に入っているのがPure AppleScriptのオブジェクトなのかCocoaのオブジェクトなのかを判定する必要があるのです。

そこで、このようにして(↓)NSObjectであるかどうかを判定できます。結果が0でないとCocoaのオブジェクト(NSObject)と判定できます。

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

set theValue to “10″
set aRes to (count {theValue} each reference)
–> 0

★Click Here to Open This Script 

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

set theValue to current application’s NSString’s stringWithString:“ABC”
set aRes to (count {theValue} each reference)
–> 1

★Click Here to Open This Script 

as anything

これは見たこともない記述です。castするときに、元のデータ型のままにするという意味のようです。AppleのWebサイトにも記述がなく、860ページもある書籍「AppleScriptリファレンス」にも、Appleが出版していた書籍「AppleScript言語ガイド」にも記載はありません(この時点で知ったので、自分の本にも記載はありません)。

長年、全世界のAppleScriptの情報を収集、分類、評価してきましたが、正直この記述に出会ったのはこれがはじめてです。にもかかわらず、明確にAppleScriptの予約語として存在していることが(Script Editorの構文色分け機能で)確認できました。

「こんな得体の知れない機能をどこで使うのか?」という話になりますが、AppleScriptのオブジェクトにcast可能なNSObject(NSArray, NSDictinaryやNSStringなどなど)がそのままの状態でPure AppleScriptのパートに受け渡されることを防ぐために記述するようです。

as anythingでcastすると、Pure AppleScriptのオブジェクトはそのままの状態で、Cocoaのオブジェクトはcast可能なPure AppleScriptのオブジェクトにcastされます。

では、Pure AppleScriptのオブジェクトに変換できないCocoaオブジェクトはどうなるのかという話になりますが、変換できずそのままです。

set b to 10
set a to b as anything
–> 10

set c to “ABC”
set d to c as anything
–> “ABC”

set e to missing value
set f to e as anything
–> missing value

★Click Here to Open This Script 

これを、ASOCのScript中で記述してコンパイル(構文確認+中間言語変換)を行うと、「as anything」が「as list of string or string」に置き換えられます。

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

set b to 10
set a to b as list of string or string –as anythingと書いておいたものが、構文確認時に置換される
–> 10

set c to current application’s NSString’s stringWithString:“ABC”
set d to c as list of string or string
–> “ABC”

set e to current application’s NSIndexSet’s new()
set f to e as list of string or string
–>  (_NSCachedIndexSet) <_nscachedindexset : 0x60800042c900>(no indexes)–Cast不能なCocoaオブジェクト

★Click Here to Open This Script 

「 list of string」({”111″, “2222″}) or 「string」(”1111″)といっておきながら数値がそのまま通るあたりに記述内容と実行結果の乖離を感じますが、元がanythingだと考えればそういうものなんでしょう。コメントなしにこの記述が書かれていたら首をひねってしまうところです。

一応、Classic Macエミュレータの「SheepSaver」上で動作するClassic Mac OS 8.6上のAppleScript J1-1.3.7で「as anything」の動作を確認してみたら、問題なく動作していました。そんな昔からあったのか〜(ーー;;;

anything3.png

ASObjC Explorer 4のScript用語辞書の作成機能で指定可能なデータ型を確認してみたら、「any」という項目がありました。このanyがanythingに該当するものだろうかと。

anything4.png

ただ、こういう「マニアックかつマイナーな仕様」は実際にテストしてみないと本当に使えるのかどうか不明です(とくに、英語環境以外では)。

実際のところ、applescript-stdlibの中には日本語環境で構文確認が通らないもの(TestTools.scptd)もあり、あまりマニアックな仕様に走りすぎると動作すらしないという見本にもなっています。

2017/11/24 Mark Alldritの「MarksLib」

AppleScript DebuggerのメーカーであるLate Night SoftwareのMark Alldrittによる「MarksLib」(Version 1.0)がTwitter上で紹介されていました。

リリースされたのは今年の5月らしく、リリースされてからけっこうな日がたっていたようです(知らなかった〜)。

AppleScript Librariesについては幾人かのデベロッパーが公開し、日々利用しています。事実上、OSAXの代替のような存在になっており、AppleScriptにまとまった拡張機能を提供するものとして機能しています。

ただ、作成・提供する側のメリットが少なく(フリー配布だと)、何かのソフトウェアや書籍を販売するための「呼び水」として利用できる開発者のみが提供している、という印象。

# 自分もコマンドラインからosascript経由でAppleScriptを呼び出す書籍を企画していたときに、osascript環境にたりない機能を補う「piyoLib」を作成していましたが、企画が流れてお蔵入りしています

そんな中、最も有名なのはShane Stanleyによるライブラリで、多種多用かつ高機能な内容です。どちらかといえば「AppleScriptObjCを覚えたユーザー」向けの高度な内容といえます。

Markのライブラリは、Shaneのものよりも基礎的な内容をねらったもので、AppleScriptObjCもCocoaもわからなくても利用できるものです。

一番の特徴は、Github上でオープンソースで公開されており、インストール方法もコマンドライン上から操作するだけ。「こういうやり方もあるのか」と、参考になります。

MarkLib v1.0にはAppleScript用語辞書(sdef)はついていません。

ためしに、MarksLib中のreadFromFile()ハンドラで各種文字エンコーディングのテキストの読み込みを試してみたところ、シフトJISのみ読み込めました(UTF-8をはじめ、他のエンコーディングも全滅)。日本語環境では実用性はありません。

内容を読んでみても「とりあえず動く」というレベルの最低限のものなので、Script Librariesの「サンプル」というレベルだと理解しました。

とりあえず、Github上でオープンソースのライブラリを公開して、配布するという「やりかた」が参考になりそうです。

2017/11/22 Finder上で選択中の画像を横方向に連結

Finder上で選択中の画像ファイルを横方向に連結してデスクトップにPNG形式で書き出すAppleScriptです。

Finder上で選択中の画像ファイルに対して、

finder_selection.png

それらが画像ファイルかどうかを確認し、画像ファイルであれば横方向に(10pointの隙間を作って)連結してデスクトップ上にPNG形式で書き出します。

c524ba57-f107-4605-92c9-dfb95720cf31_resized.png

こんなふうに(↑)。

Retina解像度対策(x2)は行なっていないので、解像度の異なる画像同士を連結しようとすると問題が(解像度の不一致による極端なサイズの違い)出る可能性があります。

なお、指定パスからのUTIツリーの取得にオープンソースのフレームワーク「MagicKit」を使用しています。本Scriptの実行にはGithub上のプロジェクトをダウンロードして各自でXcode上でFrameworkをビルドし、MagicKit.frameworkを~/Library/Frameworksフォルダ以下にインストールする必要があります。

AppleScript名:Finder上で選択中の画像を横方向に連結
– Created 2017-11-21 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “QuartzCore”
use framework “AppKit”
use framework “MagicKit” –https://github.com/aidansteele/magickit
–http://piyocast.com/as/archives/4995

property NSMutableArray : a reference to current application’s NSMutableArray
property NSArray : a reference to current application’s NSArray
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|
property GEMagicKit : a reference to current application’s GEMagicKit

property xGap : 10 –連結時の画像間のアキ(横方向)

tell application “Finder”
  set aSel to selection as alias list
  
if aSel = {} or aSel = “” then return
end tell

–選択した画像をArrayに入れる
set imgList to NSMutableArray’s new()
repeat with i in aSel
  set aPath to POSIX path of i
  
  
–指定ファイルのUTIを取得して、画像(public.image)があれば処理を行う
  
set aRes to (GEMagicKit’s magicForFileAtPath:aPath)
  
set utiList to (aRes’s uniformTypeHierarchy()) as list
  
if “public.image” is in utiList then
    set aNSImage to (NSImage’s alloc()’s initWithContentsOfFile:aPath)
    (
imgList’s addObject:aNSImage)
  end if
end repeat

–KVCで画像の各種情報をまとめて取得
set sizeList to (imgList’s valueForKeyPath:“size”) as list –NSSize to list of record conversion
set maxHeight to ((NSArray’s arrayWithArray:sizeList)’s valueForKeyPath:“@max.height”) as real
set totalWidth to ((NSArray’s arrayWithArray:sizeList)’s valueForKeyPath:“@sum.width”) as real
set totalCount to ((NSArray’s arrayWithArray:sizeList)’s valueForKeyPath:“@count”) as integer

–出力画像作成
set tSize to current application’s NSMakeSize((totalWidth + (xGap * totalCount)), maxHeight)
set newImage to NSImage’s alloc()’s initWithSize:tSize

–順次画像を新規画像に上書き
set xOrig to 0
repeat with i in (imgList as list)
  set j to contents of i
  
set curSize to j’s |size|()
  
set aRect to {xOrig, (maxHeight - (curSize’s height())), (curSize’s width()), (curSize’s height())}
  
set newImage to composeImage(newImage, j, aRect) of me
  
set xOrig to (curSize’s width()) + xGap
end repeat

–デスクトップにPNG形式でNSImageをファイル保存
set aDesktopPath to current application’s NSHomeDirectory()’s stringByAppendingString:“/Desktop/”
set savePath to aDesktopPath’s stringByAppendingString:((NSUUID’s UUID()’s UUIDString())’s stringByAppendingString:“.png”)
set fRes to saveNSImageAtPathAsPNG(newImage, savePath) 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

–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/11/22 NSColorのカラースペース変換

Cocoaの機能を用いてカラースペース変換を行なってみたAppleScriptです。

あまり本気で使うつもりはない機能ですが、ものは試しということで動作確認してみました。

c524ba57-f107-4605-92c9-dfb95720cf31_resized.png

選択色をRGBから指定のカラースペース中の色に変換し、それをふたたびRGBに戻して色表示を行います。

AppleScript名:選択色をカラースペース(RGB→グレースケール→RGB)変換して比較
– Created 2017-11-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/4993

set {rCol, gCol, bCol} to choose color

set rCol to rCol / 65535
set gCol to gCol / 65535
set bCol to bCol / 65535
log {rCol, gCol, bCol}

set aCol to current application’s NSColor’s colorWithCalibratedRed:rCol green:gCol blue:bCol alpha:1

set bCol to aCol’s colorUsingColorSpaceName:“NSDeviceWhiteColorSpace”
if bCol = missing value then error “Color Space Conversion Error (1)”

set cCol to bCol’s colorUsingColorSpaceName:“NSDeviceRGBColorSpace”
if cCol = missing value then error “Color Space Conversion Error (2)”

set r2Col to (cCol’s redComponent())
set g2Col to (cCol’s greenComponent())
set b2Col to (cCol’s blueComponent())
log {r2Col, g2Col, b2Col}

choose color default color {r2Col * 65535, g2Col * 65535, b2Col * 65535}

★Click Here to Open This Script 

AppleScript名:選択色をカラースペース(RGB→CMYK→RGB)変換して比較
– Created 2017-11-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/4993

set {rCol, gCol, bCol} to choose color

set rCol to rCol / 65535
set gCol to gCol / 65535
set bCol to bCol / 65535
log {rCol, gCol, bCol}

set aCol to current application’s NSColor’s colorWithCalibratedRed:rCol green:gCol blue:bCol alpha:1

set bCol to aCol’s colorUsingColorSpaceName:“NSDeviceCMYKColorSpace”
if bCol = missing value then error “Color Space Conversion Error (1)”

set cCol to bCol’s colorUsingColorSpaceName:“NSDeviceRGBColorSpace”
if cCol = missing value then error “Color Space Conversion Error (2)”

set r2Col to (cCol’s redComponent())
set g2Col to (cCol’s greenComponent())
set b2Col to (cCol’s blueComponent())
log {r2Col, g2Col, b2Col}

choose color default color {r2Col * 65535, g2Col * 65535, b2Col * 65535}

★Click Here to Open This Script 

2017/11/21 twoDdictKitのじっけん v1

オープンソースの「AGTTwoDimensionalDictionary」(By Agant Ltd.)をフレームワーク化したtwoDdictKit.frameworkを呼び出すAppleScriptです。

AGTTwoDimensionalDictionary「kd木」を実装したもので、 2次元ユークリッド空間の座標を扱っています。2次元座標を扱うものの、配列変数ではないので0.1とか-10といった整数値ではない数値による座標を指定可能です。

と自分で書いておきながら「これって何に使えるの?」という疑問からテストしたものだったのと、本フレームワーク自体がそれほど機能を実装されているものではないため、現時点では「とりあえずためしてみました」レベル以上のものではありません。

掲載のAppleScriptでは、サブルーチンとの間のデータのやりとりを値渡しではなく参照渡しで行っています。ふだんは、なるべく単純なプログラムとデータ構造で記述するようにしているので、極力参照渡しは避けているのですが、このデータについては参照渡しでないと手が出なかったもので。

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

–> Download towddictkit.framework binary

AppleScript名:twoDdictKitのじっけん v1
– Created 2016-03-03 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “towDdictKit” –https://github.com/AgantLtd/twoddict
–http://piyocast.com/as/archives/4991

set aDict to current application’s AGTTwoDimensionalDictionary’s new()
setData(aDict, “Hello”, 0, 0) of me
set aRes to (getData(aDict, 0, 0) of me) as string
–>  ”Hello”

setData(aDict, “Goodbye”, 1, 1) of me
set aRes to (getData(aDict, 1, 1) of me) as string
–>  ”Goodbye”

removeData(aDict, 1, 1) of me
set aRes to (getData(aDict, 1, 1) of me) as string
–>  ”missing value”

–Set Data By Reference
on setData(aDict, aData, aDim, bDim)
  aDict’s setObject:aData atLocation:(addr(aDim, bDim) of me)
end setData

–Return Data By Reference
on getData(aDict, aDim, bDim)
  set aVal to aDict’s objectAtLocation:(addr(aDim, bDim) of me)
end getData

–Return Data By Reference
on removeData(aDict, aDim, bDim)
  aDict’s removeObjectAtLocation:(addr(aDim, bDim) of me)
end removeData

on addr(d1 as number, d2 as number)
  return current application’s CGPointMake(d1, d2)
end addr

★Click Here to Open This Script 

2017/11/20 主要なタイムゾーンのカレンダーの開始曜日を取得して集計

macOS内に定義されているタイムゾーン(knownTimeZoneNames)のカレンダーの開始曜日を取得して、firstWeekdayを集計するAppleScriptです。

取得するScriptに集計部分を追加しただけのものです。

AppleScript名:主要なタイムゾーンのカレンダーの開始曜日を取得して集計
– Created 2017-11-15 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4990

property NSDate : a reference to current application’s NSDate
property NSCalendar : a reference to current application’s NSCalendar
property NSOrderedSet : a reference to current application’s NSOrderedSet
property NSLocale : a reference to current application’s NSLocale
property NSCountedSet : a reference to current application’s NSCountedSet
property NSMutableArray : a reference to current application’s NSMutableArray
property NSTimeZone : a reference to current application’s NSTimeZone

set currentCalendar to NSCalendar’s currentCalendar()
set tzList to (NSTimeZone’s knownTimeZoneNames()) as list

set fList to {}
repeat with i in tzList
  set j to contents of i
  (
currentCalendar’s setLocale:(NSLocale’s localeWithLocaleIdentifier:j))
  
  
set aTZ to (NSTimeZone’s timeZoneWithName:j)
  
set theComponents to (currentCalendar’s componentsInTimeZone:aTZ fromDate:(NSDate’s |date|()))
  
set tmpCalend to theComponents’s calendar()
  
  
set aFirstDay to (tmpCalend’s firstWeekday) as integer –firstWeekday: 1=Sunday, 2=Monday
  
set the end of fList to aFirstDay
end repeat

set aRes to calcFrequency(fList) of me
–> {{theKey:1, theCount:371}, {theKey:2, theCount:66}}

–1D Listのデータを各要素ごとに出現頻度集計
on calcFrequency(fList)
  set f2List to makeUniqueListFrom(fList) of me
  
  
set theCountedSet to NSCountedSet’s alloc()’s initWithArray:fList
  
set newArray to NSMutableArray’s new()
  
repeat with i in f2List
    (newArray’s addObject:{theKey:i, theCount:(theCountedSet’s countForObject:i)})
  end repeat
  
return newArray as list
end calcFrequency

–リスト内容のユニーク化
on makeUniqueListFrom(theList)
  set theSet to NSOrderedSet’s orderedSetWithArray:theList
  
return (theSet’s array()) as list
end makeUniqueListFrom

★Click Here to Open This Script 

2017/11/20 主要なタイムゾーンのカレンダーの開始曜日を取得

macOS内に定義されているタイムゾーン(knownTimeZoneNames)のカレンダーの開始曜日を取得するAppleScriptです。

macOS内に定義されているタイムゾーンは437あり、当然のことながら時差は±12h(24のタイムゾーン)しかないので、それぞれのタイムゾーンの「時差」は重複しています。

どちらかといえば、各国が採用しているカレンダーの仕様(どの曜日からはじまるか)を調べるために書いてみたものです。各国の首都が所在しているタイムゾーンを求め、そこのカレンダーの仕様を調べられるとよかったのですが、OS内部のAPIだけでは実現できなかったので、かわりに「knownTimeZoneNames」を呼び出してみた次第です。

週の開始日を示すfirstWeekdayは1:Sunday, 2:Monday….となっており、多くのT imezoneがSundayであることが見てとれます。

自分が位置しているタイムゾーン「Asia/Tokyo」は2(=Monday)とありました。

ただし、実際にシステム環境設定で「言語と地域」をオープンしてみると、

system_resized.png

日曜日(Sunday)。個人的にも日曜日はじまりで違和感がありません。はて?

ちなみに、1週間の開始曜日は海外のクライアントと仕事をするうえで、(日本の日常会話における)天気の話なみに重要な項目です。「常識」が常識ではないことの確認を行うことは重要です。

AppleScript名:主要なタイムゾーンのカレンダーの開始曜日を取得
– Created 2017-11-14 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4988

set currentCalendar to current application’s NSCalendar’s currentCalendar()
set knownTimeZoneNameList to current application’s NSTimeZone’s knownTimeZoneNames() as list

set fList to {}
repeat with i in knownTimeZoneNameList
  set j to contents of i
  (
currentCalendar’s setLocale:(current application’s NSLocale’s localeWithLocaleIdentifier:j))
  
  
set aTZ to (current application’s NSTimeZone’s timeZoneWithName:j)
  
set theComponents to (currentCalendar’s componentsInTimeZone:aTZ fromDate:(current application’s NSDate’s |date|()))
  
set tmpCalend to theComponents’s calendar()
  
set aFirstDay to (tmpCalend’s firstWeekday) as integer
  
set the end of fList to aFirstDay
end repeat

return fList –firstWeekday: 1=Sunday, 2=Monday

★Click Here to Open This Script 

2017/11/18 指定文字の花文字を取得 v1.2

OS内にインストールされているフォントのうち収録グリフ数が8,000以上のフォントから50個をリストアップして、各フォントで花文字を作成してCotEditor上に花文字で新規ドキュメントを作成するAppleScriptです。

注意:ただし、本ScriptはRetina Display未対応です

flowerchar3_resized.png

フォント50個に限定しているのは、大量にCotEditorでドキュメントを作成するとCotEditorのパフォーマンスが大幅に低下するためです(メモリの都合? 常識的な挙動なので問題ではありません。ドキュメントの開きすぎで)。

なお、OS内にインストールされているフォントの数が極端に少ない環境(条件に合致するフォントが50個ないとか)ではエラーになる可能性があります。

flowerchar2_resized.png

CotEditor上に50個の未保存のドキュメントができてしまうので、一括で破棄するAppleScriptを掲載しておきます。

2017-11-18-22_12_04.gif

tell application “CotEditor”
  tell every document
    close without saving
  end tell
end tell

★Click Here to Open This Script 

AppleScript名:指定文字の花文字を取得 v1.2
– Created 2017-11-18 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/4984

property NSArray : a reference to current application’s NSArray
property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSBezierPath : a reference to current application’s NSBezierPath
property NSMutableParagraphStyle : a reference to current application’s NSMutableParagraphStyle
property NSPNGFileType : a reference to current application’s NSPNGFileType
property NSFontAttributeName : a reference to current application’s NSFontAttributeName
property NSKernAttributeName : a reference to current application’s NSKernAttributeName
property NSLigatureAttributeName : a reference to current application’s NSLigatureAttributeName
property NSFont : a reference to current application’s NSFont
property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString
property NSUnderlineStyleAttributeName : a reference to current application’s NSUnderlineStyleAttributeName
property NSImage : a reference to current application’s NSImage
property NSParagraphStyleAttributeName : a reference to current application’s NSParagraphStyleAttributeName
property NSString : a reference to current application’s NSString
property NSForegroundColorAttributeName : a reference to current application’s NSForegroundColorAttributeName
property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep
property NSColor : a reference to current application’s NSColor
property NSColorSpace : a reference to current application’s NSColorSpace
property NSFontManager : a reference to current application’s NSFontManager
property NSPredicate : a reference to current application’s NSPredicate

set aString to “ぴ”

set fRes to getEveryFontPSNameANdGlyphsNum() of me
set theArray to current application’s NSArray’s arrayWithArray:fRes
set thePred to current application’s NSPredicate’s predicateWithFormat:“fontNum > 8000″
set bArray to (theArray’s filteredArrayUsingPredicate:thePred) as list
set cArray to items 1 thru 50 of bArray

repeat with i in cArray
  set aFontName to contents of i
  
set fRes to getHanamojiStr(18, fontName of aFontName, aString, 0.7, true) of me
  
makeNewCotEditorDoc(fRes) of me
end repeat

–花文字文字列を計算して返す
on getHanamojiStr(aFontSize, aFontName, aString, aThread, incFontName)
  if length of aString is not equal to 1 then return false
  
  
set aThreadShould to 768 * aThread
  
  
set fillColor to NSColor’s whiteColor –塗り色
  
set bString to aString & ” “
  
set anAssrStr to makeRTFfromParameters(bString, aFontName, aFontSize, -2, (aFontSize * 1.2)) of me
  
set aSize to anAssrStr’s |size|()
  
  
if class of aSize = record then
    set attrStrWidth to width of aSize
    
set attrStrHeight to height of aSize
  else if class of aSize = list then –macOS 10.13.xのバグ回避
    copy aSize to {attrStrWidth, attrStrHeight}
  end if
  
  
set {xPos, yPos} to {0, 0}
  
  
–下地の画像を作成
  
set tmpImg1 to makeImageWithFilledColor(attrStrWidth, attrStrHeight, fillColor) of me
  
  
–下地の画像の上にAttributed Stringを描画
  
set tmpImg2 to drawAttributedStringsOnImage(tmpImg1, anAssrStr, xPos, yPos) of me
  
  
–NSImageからRaw画像を作成
  
set aRawimg to NSBitmapImageRep’s imageRepWithData:(tmpImg2’s TIFFRepresentation())
  
  
–画像から順次指定座標の色データを拾って花文字listに反映
  
set strList to {}
  
repeat with y from 1 to attrStrHeight - 1
    
    
set strListX to {}
    
repeat with x from 0 to attrStrWidth - 1
      set tmpCol to getColorFromRawImage(aRawimg, x, y) of me
      
      
if tmpCol is not equal to false then
        if tmpCol is not equal to {255, 255, 255} then
          copy tmpCol to {tmpR, tmpG, tmpB}
          
if (tmpR + tmpG + tmpB) < aThreadShould then
            set the end of strListX to aString
          else
            set the end of strListX to “ ”
          end if
        else
          set the end of strListX to “ ”
        end if
      end if
      
    end repeat
    
set the end of strList to strListX
  end repeat
  
  
–2D List→Text
  
set aRes to list2dToStringByUsingDelimiters(strList, “ ”, return) of me
  
  
if incFontName = true then
    set fName to getDisplayedNameOfFont(aFontName) of me
    
set aRes to “■” & fName & return & return & aRes
  end if
  
  
return aRes
end getHanamojiStr

–指定Raw画像中の指定座標のピクセルの色をRGBで取り出す
on getColorFromRawImage(aRawimg, x, y)
  set origColor to (aRawimg’s colorAtX:x y:y)
  
set srgbColSpace to NSColorSpace’s deviceRGBColorSpace
  
if srgbColSpace = missing value then return false
  
  
set aColor to (origColor’s colorUsingColorSpace:srgbColSpace)
  
  
set aRed to (aColor’s redComponent()) * 255
  
set aGreen to (aColor’s greenComponent()) * 255
  
set aBlue to (aColor’s blueComponent()) * 255
  
  
return {aRed as integer, aGreen as integer, aBlue as integer}
end getColorFromRawImage

–画像のうえに指定のスタイル付きテキストを描画して画像を返す
on drawAttributedStringsOnImage(anImage, anAssrStr, xPos, yPos)
  anImage’s lockFocus()
  
anAssrStr’s drawAtPoint:(current application’s NSMakePoint(xPos, yPos))
  
anImage’s unlockFocus()
  
return anImage
end drawAttributedStringsOnImage

–書式つきテキストを組み立てる
on makeRTFfromParameters(aStr as string, fontName as string, aFontSize as real, aKerning as real, aLineSpacing as real)
  set aVal1 to NSFont’s fontWithName:fontName |size|:aFontSize
  
set aKey1 to (NSFontAttributeName)
  
  
set aVal2 to NSColor’s blackColor()
  
set aKey2 to (NSForegroundColorAttributeName)
  
  
set aVal3 to aKerning
  
set akey3 to (NSKernAttributeName)
  
  
set aVal4 to 0
  
set akey4 to (NSUnderlineStyleAttributeName)
  
  
set aVal5 to 2 –all ligature ON
  
set akey5 to (NSLigatureAttributeName)
  
  
set aParagraphStyle to NSMutableParagraphStyle’s alloc()’s init()
  
aParagraphStyle’s setMinimumLineHeight:(aLineSpacing)
  
aParagraphStyle’s setMaximumLineHeight:(aLineSpacing)
  
set akey7 to (NSParagraphStyleAttributeName)
  
  
set keyList to {aKey1, aKey2, akey3, akey4, akey5, akey7}
  
set valList to {aVal1, aVal2, aVal3, aVal4, aVal5, aParagraphStyle}
  
set attrsDictionary to NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList
  
  
set attrStr to NSMutableAttributedString’s alloc()’s initWithString:aStr attributes:attrsDictionary
  
return attrStr
end makeRTFfromParameters

–指定サイズの画像を作成し、背景を指定色で塗る
on makeImageWithFilledColor(aWidth, aHeight, fillColor)
  set anImage to NSImage’s alloc()’s initWithSize:(current application’s NSMakeSize(aWidth, aHeight))
  
  
anImage’s lockFocus()
  
set theRect to {{x:0, y:0}, {width:aWidth, height:aHeight}}
  
set theNSBezierPath to NSBezierPath’s bezierPath
  
theNSBezierPath’s appendBezierPathWithRect:theRect
  
fillColor’s |set|()
  
theNSBezierPath’s fill()
  
anImage’s unlockFocus()
  
  
return anImage
end makeImageWithFilledColor

–2D Listをアイテム間および行間のデリミタを個別に指定してテキスト変換
on list2dToStringByUsingDelimiters(aList, itemDelimiter, lineDelimiter)
  set outList to {}
  
repeat with i in aList
    set aStr to listToStringUsingTextItemDelimiter(i, itemDelimiter) of me
    
set the end of outList to aStr
  end repeat
  
  
set bStr to listToStringUsingTextItemDelimiter(outList, lineDelimiter) of me
  
return bStr
end list2dToStringByUsingDelimiters

on listToStringUsingTextItemDelimiter(sourceList, textItemDelimiter)
  set CocoaArray to NSArray’s arrayWithArray:sourceList
  
set CocoaString to CocoaArray’s componentsJoinedByString:textItemDelimiter
  
return (CocoaString as string)
end listToStringUsingTextItemDelimiter

on getEveryFontPSNameANdGlyphsNum()
  set aFontList to NSFontManager’s sharedFontManager()’s availableFonts()
  
set thePred to NSPredicate’s predicateWithFormat:“NOT SELF BEGINSWITH ’.’”
  
set aFontList to (aFontList’s filteredArrayUsingPredicate:thePred) as list
  
  
set aList to {}
  
repeat with i in aFontList
    set aName to contents of i
    
set aNum to countNumberOfGlyphsInFont(aName) of me
    
set the end of aList to {fontName:aName, fontNum:aNum}
  end repeat
  
  
return aList
end getEveryFontPSNameANdGlyphsNum

–指定Postscript名称のフォントに定義されている文字数を数えて返す
on countNumberOfGlyphsInFont(fontName)
  set aFont to current application’s NSFont’s fontWithName:fontName |size|:9.0
  
if aFont = missing value then return false
  
set aProp to aFont’s numberOfGlyphs()
  
return aProp
end countNumberOfGlyphsInFont

–フォントのPostScript NameからDisplayed Nameを取得
on getDisplayedNameOfFont(aName)
  set aFont to current application’s NSFont’s fontWithName:aName |size|:9.0
  
set aDispName to (aFont’s displayName()) as string
  
return aDispName
end getDisplayedNameOfFont

on makeNewCotEditorDoc(aCon)
  tell application “CotEditor”
    set newDoc to make new document
    
tell newDoc
      set contents to aCon
    end tell
  end tell
end makeNewCotEditorDoc

★Click Here to Open This Script 

2017/11/17 指定文字の花文字を取得 v1

指定文字の花文字を取得するAppleScriptです。

注意:ただし、本ScriptはRetina Display未対応です

hanamoji1.png

指定文字のStyledStringを作成して画像に描画し、各座標の色データを取得して判定しています。こんな処理がAppleScriptだけで記述できるようになったことは、素直に驚きです。

ブランクのNSImageにスタイル付きテキストで描画し、Raw画像の各座標からカラーデータを抽出して2D Listに反映させ、最終テキストに変換して0.1秒(MacBook Pro Retina 2012 Core i7 2.66GHz)ぐらいです。色ピックアップ部分がオーバーヘッドになっていると思います。

AppleScript名:指定文字の花文字を取得 v1.1.1
– Created 2017-11-16 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/4843

property NSArray : a reference to current application’s NSArray
property NSMutableDictionary : a reference to current application’s NSMutableDictionary
property NSBezierPath : a reference to current application’s NSBezierPath
property NSMutableParagraphStyle : a reference to current application’s NSMutableParagraphStyle
property NSPNGFileType : a reference to current application’s NSPNGFileType
property NSFontAttributeName : a reference to current application’s NSFontAttributeName
property NSKernAttributeName : a reference to current application’s NSKernAttributeName
property NSLigatureAttributeName : a reference to current application’s NSLigatureAttributeName
property NSFont : a reference to current application’s NSFont
property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString
property NSUnderlineStyleAttributeName : a reference to current application’s NSUnderlineStyleAttributeName
property NSImage : a reference to current application’s NSImage
property NSParagraphStyleAttributeName : a reference to current application’s NSParagraphStyleAttributeName
property NSString : a reference to current application’s NSString
property NSForegroundColorAttributeName : a reference to current application’s NSForegroundColorAttributeName
property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep
property NSColor : a reference to current application’s NSColor
property NSColorSpace : a reference to current application’s NSColorSpace

set aString to “長”
set fRes to getHanamojiStr(24, “HiraginoSans-W0″, aString) of me

–花文字文字列を計算して返す
on getHanamojiStr(aFontSize, aFontName, aString)
  if length of aString is not equal to 1 then return false
  
  
set fillColor to NSColor’s whiteColor –塗り色
  
set bString to aString & ” “
  
set anAssrStr to makeRTFfromParameters(bString, aFontName, aFontSize, -2, (aFontSize * 1.2)) of me
  
set aSize to anAssrStr’s |size|()
  
  
if class of aSize = record then
    set attrStrWidth to width of aSize
    
set attrStrHeight to height of aSize
  else if class of aSize = list then –macOS 10.13.xのバグ回避
    copy aSize to {attrStrWidth, attrStrHeight}
  end if
  
  
set {xPos, yPos} to {0, 0}
  
  
–下地の画像を作成
  
set tmpImg1 to makeImageWithFilledColor(attrStrWidth, attrStrHeight, fillColor) of me
  
  
–下地の画像の上にAttributed Stringを描画
  
set tmpImg2 to drawAttributedStringsOnImage(tmpImg1, anAssrStr, xPos, yPos) of me
  
  
–NSImageからRaw画像を作成
  
set aRawimg to NSBitmapImageRep’s imageRepWithData:(tmpImg2’s TIFFRepresentation())
  
  
–画像から順次指定座標の色データを拾って花文字listに反映
  
set strList to {}
  
repeat with y from 2 to attrStrHeight - 2
    set strListX to {}
    
repeat with x from 0 to attrStrWidth - 1
      set tmpCol to getColorFromRawImage(aRawimg, x, y) of me
      
      
if tmpCol is not equal to false then
        if tmpCol is not equal to {255, 255, 255} then
          copy tmpCol to {tmpR, tmpG, tmpB}
          
if (tmpR + tmpG + tmpB) < 500 then
            set the end of strListX to aString
          else
            set the end of strListX to “ ”
          end if
        else
          set the end of strListX to “ ”
        end if
      end if
      
    end repeat
    
set the end of strList to strListX
  end repeat
  
  
–2D List→Text
  
set aRes to list2dToStringByUsingDelimiters(strList, “ ”, return) of me
  
return aRes
end getHanamojiStr

–指定Raw画像中の指定座標のピクセルの色をRGBで取り出す
on getColorFromRawImage(aRawimg, x, y)
  set origColor to (aRawimg’s colorAtX:x y:y)
  
set srgbColSpace to NSColorSpace’s deviceRGBColorSpace
  
if srgbColSpace = missing value then return false
  
  
set aColor to (origColor’s colorUsingColorSpace:srgbColSpace)
  
  
set aRed to (aColor’s redComponent()) * 255
  
set aGreen to (aColor’s greenComponent()) * 255
  
set aBlue to (aColor’s blueComponent()) * 255
  
  
return {aRed as integer, aGreen as integer, aBlue as integer}
end getColorFromRawImage

–画像のうえに指定のスタイル付きテキストを描画して画像を返す
on drawAttributedStringsOnImage(anImage, anAssrStr, xPos, yPos)
  anImage’s lockFocus()
  
anAssrStr’s drawAtPoint:(current application’s NSMakePoint(xPos, yPos))
  
anImage’s unlockFocus()
  
return anImage
end drawAttributedStringsOnImage

–書式つきテキストを組み立てる
on makeRTFfromParameters(aStr as string, fontName as string, aFontSize as real, aKerning as real, aLineSpacing as real)
  set aVal1 to NSFont’s fontWithName:fontName |size|:aFontSize
  
set aKey1 to (NSFontAttributeName)
  
  
set aVal2 to NSColor’s blackColor()
  
set aKey2 to (NSForegroundColorAttributeName)
  
  
set aVal3 to aKerning
  
set akey3 to (NSKernAttributeName)
  
  
set aVal4 to 0
  
set akey4 to (NSUnderlineStyleAttributeName)
  
  
set aVal5 to 2 –all ligature ON
  
set akey5 to (NSLigatureAttributeName)
  
  
set aParagraphStyle to NSMutableParagraphStyle’s alloc()’s init()
  
aParagraphStyle’s setMinimumLineHeight:(aLineSpacing)
  
aParagraphStyle’s setMaximumLineHeight:(aLineSpacing)
  
set akey7 to (NSParagraphStyleAttributeName)
  
  
set keyList to {aKey1, aKey2, akey3, akey4, akey5, akey7}
  
set valList to {aVal1, aVal2, aVal3, aVal4, aVal5, aParagraphStyle}
  
set attrsDictionary to NSMutableDictionary’s dictionaryWithObjects:valList forKeys:keyList
  
  
set attrStr to NSMutableAttributedString’s alloc()’s initWithString:aStr attributes:attrsDictionary
  
return attrStr
end makeRTFfromParameters

–指定サイズの画像を作成し、背景を指定色で塗る
on makeImageWithFilledColor(aWidth, aHeight, fillColor)
  set anImage to NSImage’s alloc()’s initWithSize:(current application’s NSMakeSize(aWidth, aHeight))
  
  
anImage’s lockFocus()
  
set theRect to {{x:0, y:0}, {width:aWidth, height:aHeight}}
  
set theNSBezierPath to NSBezierPath’s bezierPath
  
theNSBezierPath’s appendBezierPathWithRect:theRect
  
fillColor’s |set|()
  
theNSBezierPath’s fill()
  
anImage’s unlockFocus()
  
  
return anImage
end makeImageWithFilledColor

–2D Listをアイテム間および行間のデリミタを個別に指定してテキスト変換
on list2dToStringByUsingDelimiters(aList, itemDelimiter, lineDelimiter)
  set outList to {}
  
repeat with i in aList
    set aStr to listToStringUsingTextItemDelimiter(i, itemDelimiter) of me
    
set the end of outList to aStr
  end repeat
  
  
set bStr to listToStringUsingTextItemDelimiter(outList, lineDelimiter) of me
  
return bStr
end list2dToStringByUsingDelimiters

on listToStringUsingTextItemDelimiter(sourceList, textItemDelimiter)
  set CocoaArray to NSArray’s arrayWithArray:sourceList
  
set CocoaString to CocoaArray’s componentsJoinedByString:textItemDelimiter
  
return (CocoaString as string)
end listToStringUsingTextItemDelimiter

★Click Here to Open This Script 

2017/11/16 指定UTIでUTIを入れたリストをフィルタリング

指定のUTI(Uniform Type Identifier)で、UTIを入れたリストをフィルタリングするAppleScriptです。

UTIはデータのタイプを一意に識別する文字列で、「ざっくりとしたデータ区分」から「詳細なデータ区分」まで階層的に表現できます。AppleScriptではchoose fileコマンドに「of type」のオプションがあり、ここにUTIを記述する・・・という程度のつながり。JPEG画像のみ、PDFのみファイル一覧ダイアログに表示、といった「詳細なデータ型の抽出」機能が用いられてきました。

ただ、UTI自体はもうちょっと便利な概念なので、AppleScriptでもCocoaの機能やSpotlightの機能を日常的に利用するようになってきた今日においては、より活用すべき仕組みです。

たとえば、 “public.mp3″とUTIを指定したらMP3データしか対象になりませんが、より上位の概念を示す”public.audiovisual-content”と指定すれば、さまざまな形式のオーディオ、ビデオのデータファイルが対象になります。

こうしたUTIの概念ツリーを使って「このカテゴリに含まれるデータ型はどれ?」といった演算が手軽にできるようになっているため、調べてまとめておきました(探すのには苦労しましたけれど)。

本Scriptでは、 峪慊蠅靴UTi」に含まれるものを◆UTIのリスト」から抽出します。爬虫類に含まれるものをトカゲ、ヘビ、カエルのリストに対して抽出すると、トカゲとヘビが返ってくるような感じです。

ちなみに、ファイル拡張子からUTIを求める機能はShane StanleyのAppleScript Libraries「Bridge Plus」が提供しています。

AppleScript名:指定UTIでUTIを入れたリストをフィルタリング
– Created 2017-11-03 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4978

property NSPredicate : a reference to current application’s NSPredicate
property NSArray : a reference to current application’s NSArray

set aList to {“public.jpeg”, “com.compuserve.gif”, “public.svg-image”, “public.plain-text”, “com.apple.iwork.keynote.key”, “com.apple.iwork.pages.pages”, “com.apple.iwork.numbers.numbers”, “com.microsoft.word.doc”, “com.microsoft.excel.xls”, “com.microsoft.powerpoint.ppt”, “com.apple.mail.email”, “com.apple.applescript.script”, “com.apple.applescript.text”, “public.html”, “com.apple.property-list”, “public.zip-archive”, “public.au-audio”, “com.apple.m4a-audio”, “com.apple.m4v-video”}

set aRes to filterUTIList(aList, “public.text”)
–>  {”public.plain-text”, “com.apple.applescript.script”, “com.apple.applescript.text”, “public.html”}

set bRes to filterUTIList(aList, “public.image”)
–>  {”public.jpeg”, “com.compuserve.gif”, “public.svg-image”}

set cRes to filterUTIList(aList, “public.audiovisual-content”)
–>  {”public.au-audio”, “com.apple.m4a-audio”, “com.apple.m4v-video”}

on filterUTIList(aUTIList, aUTIstr)
  set anArray to NSArray’s arrayWithArray:aUTIList
  
set aPred to NSPredicate’s predicateWithFormat_(“SELF UTI-CONFORMS-TO %@”, aUTIstr)
  
set bRes to (anArray’s filteredArrayUsingPredicate:aPred) as list
  
return bRes
end filterUTIList

★Click Here to Open This Script 

2017/11/15 FileManager Lib 2.0

ここのところ、US AppleのMailing List「AppleScript Users ML」や直メールなどでmacOS 10.13のファイル処理について、macOS 10.13のバグだか仕様だかわかりにくい状況を回避しつつ、目的のファイル操作を行うベストな方法について情報交換が行われてきました。

候補にあがっている「道具」は、Finder、System Events、Cocoa(NSFileManager)、FileManager Libです(UNIX Shellもアリかも)。

Finder

macOS 10.13.xでは、フィルター参照によるオブジェクトの絞り込みが遅く(けっこう前からこんな状況)、さらに処理対象ファイルのリネーム後の存在確認を行ってみても、処理対象がaliasであるにもかかわらず存在が確認できなくなるなど、かなり信頼性が低くなっています。

こんにち、指定フォルダ以下のすべてのファイルを取得するのにSpotlightを用いずFinderで再帰処理するのが「ありえない」のと同様、64bit環境のmacOSにおいてファイル処理を行うのにFinderを用いるのは現実的でなくなりつつあります。

System Events

macOS標準搭載の、OS側の各種機能をAppleScriptに提供する専用のGUIなしアプリケーション群(○○ Scripting/○○ Eventsという名前)のひとつ。統廃合がすすみ、目下最大の機能を持つに至ったのがこのSystem Eventsです。

ファイル処理速度もFinderより上で、(4,000個ぐらいの)大量のファイルに対してフィルタ参照による絞り込みをおこなっても、Finderのように「処理が終わらない」といったことがありません(でもCocoaによる処理よりは遅い)。ただし、「disk item」(file/folderを一意に指し示せる汎用オブジェクト)という謎のオブジェクトを使うのと、それほど使い込んで処理した覚えがない(実績がいまいち)のと、ファイルのコピーを行うコマンドが存在しない点に要注意です。

Cocoa(NSFileManager)

Cocoaの機能を利用して高速にファイル処理できますし、Finderにバグがあっても関係ありません。Cocoa Scriptingを理解していれば問題はありませんが、これをマスターしていないと使えません(ASObjC Explorer 4とかScript Debugger 6がないと事実上無理ですし)。パスはPOSIX pathを用いる必要があります。

FileManager Lib

Shane Stanleyによる各種AppleScript Librariesのうちのひとつです。

Cocoa Scriptingをマスターしていなくても、Cocoaの速度をPure AppleScript程度の難易度で実現できるというメリットがあります(Script Debugger 6はあったほうがいいでしょう)。ただ、OSに標準インストールされていないので、自分でダウンロードしてインストールする必要があります。処理対象file/folderの指定にaliasが使える一方、返り値についてはaliasを使わない傾向があります。フィルタ参照はサポートしていません(Spotlightで行えという感じでしょうか)。

と、ひととおり手段をリストアップしたうえで、FileManager Libを用いた代表的なファイル処理の書き方を紹介しておきます。

フォルダ中のファイル一覧を取得

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use fLib : script “FileManagerLib”

set aFol to choose folder
set aList to contents of aFol
—> {”/Users/me/Desktop/0001.scpt”, “/Users/me/Desktop/1013error.jpg”….}

★Click Here to Open This Script 

フォルダ中のファイル一覧をフィルタ参照で取得

なし

ファイルの存在確認

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use fLib : script “FileManagerLib”

set aFile to choose file
set tRes to exists item aFile
–> true

★Click Here to Open This Script 

ファイルの移動

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use fLib : script “FileManagerLib”

set aFile to choose file
set aFol to choose folder

move item aFile to folder aFol with replacing

★Click Here to Open This Script 

ファイル/フォルダ削除(ゴミ箱に移動)

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use fLib : script “FileManagerLib”

set aFile to choose file
set tRes to (trash item aFile)

★Click Here to Open This Script 

ファイル/フォルダ削除(即時削除)

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use fLib : script “FileManagerLib”

set aFile to choose file
set tRes to (remove item aFile)

★Click Here to Open This Script 

リネーム

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use fLib : script “FileManagerLib”

set aFile to choose file
set aInfo to info for aFile
set aExt to name extension of aInfo
set rRes to (rename item aFile to name (“New Name” & “.” & aExt))

★Click Here to Open This Script 

フォルダ作成

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use fLib : script “FileManagerLib”

set aFile to choose folder
set fRes to create folder at aFile use name “A NEW FOLDER”
–> true

★Click Here to Open This Script 

ファイルコピー

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use fLib : script “FileManagerLib”

set aFile to choose file
set aFol to choose folder
copy item aFile to folder aFol with replacing

★Click Here to Open This Script 

結論としては、macOS 10.13上ではFinderがいまいちで、System EventsやFileManager Libだと「必要とされる知識量」が少なくて済み、ひたすら処理速度を求めるならFileManager Lib(シンプル)かNSFileManager(やりたい処理をやりたいように)がおすすめといったところでしょうか。

2017/11/14 相対パスから絶対パスを計算して求める v2

相対パスから絶対パスを計算して求める( 峇霆爐箸覆訐簑丱僖后廚鮓気豊◆嵳燭┐蕕譴秦蠡丱僖后廚鬮「絶対パス」に変換する)AppleScriptです。

Markdown書類にリンクした画像のリンク切れチェックを行う必要があり、あまり考えずに作ってみました。

Markdown書類中にリンクした画像は相対パスでリンク先を記述しています。リンクについては指定画像を画像専用フォルダにコピーして相対パスを求め、リンクタグをクリップボードに入れるAppleScriptを書いて(リンクを)記述しています。

このとき、Markdown書類群のフォルダ階層構造を維持していればリンク切れは起こりませんが、一応チェックしておきたいのが人情。

そこで、チェック用のScriptを書いておくことに。だいたいはありあわせのサブルーチンを組み合わせることでSpotlight経由のMarkdown書類の検索や正規表現によるMarkdown中の画像リンク箇所の抽出など、たいした手間もかけずに記述できます。

ただ、Markdown書類のパス(絶対パス)をもとに、書類中に書かれている相対パスから実際にリンク画像が存在している絶対パスを求めるサブルーチンが存在していませんでした(OS側に用意されていてもよさそうなものですが)。

そこで、本ルーチンを書いて使ってみました。とくに問題もなく使えています。

AppleScript名:相対パスから絶対パスを計算して求める v2
– Created 2017-11-11 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4976

set absolutePath to “/Users/me/Documents/–Book 1「AppleScript最新リファレンス バージョン2」/5000 iOSデバイスとの連携/5100 iOSデバイスからMacに画面を出力するAirServer.md”
set relativePath to “../9999_images/img-1.jpeg”

set relativePath to calcAbsolutePath(absolutePath, relativePath) of me
–>  ”/Users/me/Documents/–Book 1「AppleScript最新リファレンス バージョン2」/9999_images/img-1.jpeg”

on calcAbsolutePath(aAbsolutePOSIXfile, bRelativePOSIXfile)
  set aStr to current application’s NSString’s stringWithString:aAbsolutePOSIXfile
  
set bStr to current application’s NSString’s stringWithString:bRelativePOSIXfile
  
  
set aList to aStr’s pathComponents() as list
  
set bList to bStr’s pathComponents() as list
  
  
set aLen to length of aList
  
  
set aCount to 1
  
repeat with i in bList
    set j to contents of i
    
if j is not equal to “..” then
      exit repeat
    end if
    
set aCount to aCount + 1
  end repeat
  
  
set tmp1List to items 1 thru (aLen - aCount) of aList
  
set tmp2List to items aCount thru -1 of bList
  
  
set allRes to current application’s NSString’s pathWithComponents:(tmp1List & tmp2List)
  
  
return allRes as text
end calcAbsolutePath

★Click Here to Open This Script 

2017/11/13 macOS 10.13ではファイル操作はFinderではなくSystem Eventsがおすすめ?

macOS 10.13のFinderをAppleScriptからコントロールすると、「指定のaliasをリネームした後に、aliasに対して存在確認を行うとfalseが返ってくる」という問題が見つかりました。

1フォルダ中に大量のファイルが存在していた場合の処理速度の問題もあるため、macOS 10.13ではもはや「Finderでファイル処理を行うと危ない」といえるレベル。

こっそり追加されつつも誰も本気で使っていなかった「System Eventsのファイル処理関連機能」の利用が検討すべき段階に来たように感じられます。

–http://piyocast.com/as/archives/4975
set aa to choose file

tell application “System Events”
  set a1 to (exists of aa) as string
  
set a2 to (class of aa) as string
  
set a3 to (name of aa) as string
  
  
display dialog a1 & “-” & a2 & “-” & a3
  
  
set name of aa to “changed_YYYYY”
  
  
set b1 to (exists of aa) as string
  
display dialog b1
end tell

★Click Here to Open This Script 

テストに使用したのはこのような(↑)簡単なScriptです。短いわりに破壊力は抜群で、macOS 10.13上で動かすと、リネーム後はファイルの存在(existence)がfalseで返ってきます。

macOS 10.13のFinderに問題があるのか、APFSがそもそもそのような仕様なのか、APFSとFinderの機能の整合性がとれていないのかは不明ですが、知らずに使うとハマるというか、怖い仕様です。

自分は最初からmacOS 10.13のFinderを信用せずにCocoaの機能でファイル処理を行うようにしていたので、この問題には直面していなかったのですが、誰にでもCocoa系の処理をおすすめするものではありません(手軽ではないので)。

そこで、System Eventsに実装されているファイル処理系の機能を真剣に検討してみました。

結論から言うと、Finder経由でファイル処理する場合とそれほど変わらない記述でファイル処理を記述できるため、手軽に処理できそうです。ただし、「ファイルのコピー(duplicate)」はどう書いても実行できなかったので、これ以外は大丈夫といったところでしょうか。

フォルダ中のファイル一覧を取得

定番中の定番処理ですが、ちょっとだけ書き方がFinderと違います。Finderだと結果をaliasのlistで取得するために「as alias list」といった書き方(これがない時代はけっこう大変でした)をしていましたが、これはSystem Eventsでは使えません。

また、every fileと書くとfileのlistが返ってきました(あとでaliasにcastする必要がある)。しかし、「every alias」という見たこともないような書き方をすることで、aliasのlistを取得できることが判明。びっくりです。

–http://piyocast.com/as/archives/4975
set a to choose folder

tell application “System Events”
  set aList to (every alias of a whose visible is true)
end tell

★Click Here to Open This Script 

フォルダ中のファイル一覧をフィルタ参照で取得

取得するオブジェクトを条件で抽出する「フィルタ参照」を用いて、条件に合うファイル(alias)のみを取得します。AppleScriptの定番中の定番処理です。

正直、Finderに対するフィルタ参照で、「すべての属性がフィルタ参照の対象として使えるかは(やってみないと)わからない」という状態だったので、System Eventsに対しても、ひととおり試してみる必要を感じます。

–http://piyocast.com/as/archives/4975
set a to choose folder

tell application “System Events”
  set aList to (every alias of a whose name extension is “pdf”)
end tell

★Click Here to Open This Script 

ファイルの存在確認

aliasに対してでもdisk itemに対してでも「exists」は取得できるようです。

–http://piyocast.com/as/archives/4975
set aFol to choose folder

tell application “System Events”
  tell folder (aFol as string)
    set eRes to exists of disk item “atext.txt”
    
–> true
  end tell
end tell

★Click Here to Open This Script 

ファイルの移動

指定ファイルを指定フォルダに移動させるコマンドです。ただし、Finderのmoveコマンドと異なり、移動先に同名のファイルが存在していても上書きするなどのオブションが用意されていない。
–http://piyocast.com/as/archives/4975
set aFile to choose file with prompt “Select a file to move”
set tFol to choose folder with prompt “Select target folder”

tell application “System Events”
  move (aFile as string) to (tFol as string)
end tell

★Click Here to Open This Script 

ファイル/フォルダ削除

Finderのdeleteコマンドは削除対象のファイル/フォルダをゴミ箱に移動させる命令だったが、System Eventsではいきなり削除する。
–http://piyocast.com/as/archives/4975
set a to choose file

tell application “System Events”
  set aa to (disk item (a as string))
  
delete aa
end tell

★Click Here to Open This Script 

リネーム

aliasに対してリネームを行う。System Eventsではファイルをdisk itemでアクセスする必要のあるコマンドやaliasでも処理できるコマンドが混在しているなど、いまひとつこなれていない印象がある。

–http://piyocast.com/as/archives/4975
set a to choose file

tell application “System Events”
  set aExt to name extension of a
  
set name of a to (“CCCC” & “.” & aExt)
  
return a
  
–> alias “Cherry:Users:me:Desktop:CCCC.png”
end tell

★Click Here to Open This Script 

フォルダ作成

Finderとあまり差がない。ただし、作成対象フォルダの指定はaliasを受け付けないのでas stringでパスを文字化してからfolderで指定する必要がある点に注意。

–http://piyocast.com/as/archives/4975
set a to choose folder

tell application “System Events”
  make new folder at folder (a as string) with properties {name:“new folder”}
end tell

★Click Here to Open This Script 

ひととおり、System EventsのAppleScript用語辞書を見て試してみただけのものであるため、今後もっといい書き方が発見される可能性もあります。あしからず。

2017/11/11 Metadata Lib 2.0

Shane StanleyによるSpotlight検索用のAppleScript Libraies「Metadata Lib」の新バージョン2.0が公開されました。

Metadata Libは登場頻度も高く、実際に便利であるため、万人におすすめできるライブラリです。「おすすめできないライブラリってあるのかよ?」という話もありますが、癖が強くて読みにくくて特定の言語環境(英語とか)でしか検証が行われていないライブラリは避けています。

Metadata Lib v1.0はmacOS 10.9以降を対象としていましたが、v2.0はmacOS 10.10以降を対象としています。正直、10.10はバグが多くて避けたいので、10.10以降と考えてもよいでしょう(macOS 10.10嫌い。でも、バグだらけで直る見通しすら立っていないmacOS 10.13が一番嫌い)。

Metadata Lib 1.0から2.0への変更点は、AppleScript用語辞書(sdef)が添付され、英語っぽい記法で呼び出せるようになった点です。バージョン1.0の「いわゆるサブルーチンの塊を呼び出している感」から、英文っぽいこなれた表記ができるようにテイストが変わりました。

いっぽうで、Metadata Lib v1.0を対象に書いたAppleScriptも、v2.0でそのまま使えます。

mdfind2.png

また、用例(Sample Searches.scpt)が添付されるようになったのも大きな違いです。

mdfind_resized.png

本ライブラリは実際に内容(ソース)を読むこともできるようになっており、用語辞書の用語を使って呼び出した場合とバージョン1.0的な普通のハンドラの両方が用意されていることが読み取れます。

AppleScript LibrariesはAppleScriptによってAppleScriptの予約語を拡張して機能を追加できるという、OSAXをAppleScript自身で書けるような存在で、ちょっとやりすぎな特徴として「自分自身のAppleScript用語辞書の用語を用いてライブラリを記述できる」というものがあります。

ただ、ライブラリ自身の用語辞書を用いてライブラリの内容を書くとメンテナンスがやりにくくなるので避けるべきです。

今回のMetadata Libのように「用語を使ったハンドラ」と「通常のサブルーチン的なハンドラ」の両方を用意して、基本的には通常のサブルーチンを呼び出すような記法で書いてあり、よいお手本となる内容です。

use AppleScript version “2.4″ – 10.10 or later
use framework “Foundation”
use mdLib : script “Metadata Lib” version “2.0.0″

–添付のAppleScript用語辞書を利用した英文っぽいハンドラ
on perform search predicate string predString in folders filesAliasesURLsOrPosixPaths : missing value just in fileAliasURLOrPosixPath : missing value in scopes listOfScopes : missing value search arguments argList : {} attributes to include attList : missing value converting dates datesFlag : false names only namesFlag : false
  –  
end perform search

–普通のサブルーチンっぽい(Objective-Cっぽい記法の)ハンドラ
on searchFolders:filesAliasesURLsOrPosixPaths searchString:predString searchArgs:argList
  
end searchFolders:searchString:searchArgs:

★Click Here to Open This Script 

ささっとMetadata Libの中を読んでみて驚いたのが、「using terms from scripting additions」という記述。これは見たことがありません(using termis from application “Finder” といったようにアプリケーション名を指定するものはありました)。

using terms from scripting additions
  tell (current date) to set {theASDate, year, day, its month, day, its hours, its minutes, its seconds} to {it, theYear, 1, theMonth, theDay, theHour, theMinute, theSeconds}
end using terms from

★Click Here to Open This Script