Archive for 11月, 2017

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 

2017/11/11 1Dリスト中の最も近い値を返す

1次元配列(1D List)に入っている数値のうち、パラメータの数値が最も近いものを返すAppleScriptです。

1D Listに入っている数値の刻み幅が一定でない場合(人間には見た目がすっきりしていても刻み幅が一定でないなど)に、最も近い値のものを自動でピックアップすることを目的としています。

AppleScript名:1Dリスト中の最も近い値を返す
– Created 2017-11-11 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.5″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4971
property NSArray : a reference to current application’s NSArray

set aList to {50, 30, 20, 15, 10, 5, 0}

set targNum to retNearestNumInList(7, aList) of me
–>  5

set targNum to retNearestNumInList(100, aList) of me
–> 50

on retNearestNumInList(aNum, aList as list)
  set anNSArray to NSArray’s arrayWithArray:aList
  
set bList to (anNSArray’s sortedArrayUsingSelector:“compare:”) as list
  
set aPreNum to 0
  
  
repeat with i in bList
    set j to contents of i
    
if j aNum then
      exit repeat
    end if
    
set aPreNum to j
  end repeat
  
  
return aPreNum
end retNearestNumInList

★Click Here to Open This Script 

2017/11/10 選択した画像を画像フォルダにコピーして、Markdown書式で画像パスタグをクリップボードに転送 v2

指定の画像を、書いている書籍の画像フォルダにコピーして、作成中のMarkdown書類からの相対パスを求めてMarkdown形式の画像リンクタグをクリップボードに転送するAppleScriptです。

md1.png

書籍の執筆作業は、「–」ではじまる名前の書籍ルートフォルダ以下に章ごとのフォルダを作成し、その中にMarkdown書類やPages書類を入れています。

画像については各原稿と同じフォルダ内ではなく、画像専用フォルダに配置(一般的なWebサイトの管理と同じ)。各Markdown書類には画像専用フォルダへの相対パスで画像リンクタグを入れています。

![md1.png](../9999_images/md1.png)

md2.png

本AppleScriptは、この書籍中の書籍中の画像専用フォルダに指定画像をコピーし、クリップボードに対して画像の相対パスを入れます。

書籍ルートフォルダ以下の画像専用フォルダを検索するためにSpotlight検索用の「MetaData Lib」を使用しています。

ファイルコピー時には画像専用フォルダ内のすべてのファイル名をチェックし、ファイル名の重複が発生していた場合にはファイル名末尾に連番をつけ、重複を回避しています。

前提としているMarkdownエディタは「MacDown」で、最前面の書類のパスを取得する部分でMacDownの機能を用いています。ただ、その程度の機能しか利用していないので、他のMarkdownエディタ用に転用するのも難しくないと思います(MarkdownエディタでAppleScriptに対応しているものが少数派なんですが)。

以前はMarkdown書類中にリンクする画像はDropboxにREST API経由でアップロードしてURLを用いていました。アップロード自体はそれなりですが、Markdown書類のオープン時や編集中、PDF出力時などは遅くて辟易しました。画像がローカルにあったほうが速度的なメリットが大きいと判断し、変更しました。

AppleScript名:選択した画像を画像フォルダにコピーして、Markdown書式で画像パスタグをクリップボードに転送 v2
– Created 2017-11-09 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/4968

property NSOrderedSame : a reference to current application’s NSOrderedSame
property NSFileManager : a reference to current application’s NSFileManager
property NSString : a reference to current application’s NSString

set imgPath to choose file of type {“public.image”}
set imgPOSIX to NSString’s stringWithString:(POSIX path of imgPath)
set imgFileName to (imgPOSIX’s lastPathComponent()) as string

–Get Front Document Path
tell application “MacDown”
  set dList to every document
  
if length of dList = 0 then
    display dialog “No Documents…” buttons {“OK”} default button 1
    
return
  end if
  
  
tell front document
    set aProp to properties
  end tell
  
  
set aPOSIXpath to POSIX path of ((file of aProp) as alias)
end tell

–Calculate Parent Book Folder Path
set aStr to NSString’s stringWithString:aPOSIXpath
set bStr to aStr’s stringByDeletingLastPathComponent()
set aList to bStr’s pathComponents() as list
–> {”/”, “Users”, “me”, “Documents”, “ぴよまるソフトウェア”, “書籍執筆関連、技術書典など”, “書籍原稿”, “–Book 1「AppleScript最新リファレンス」”, “5000 iOSデバイスとの連携”}

set bList to reverse of aList
–> {”5000 iOSデバイスとの連携”, “–Book 1「AppleScript最新リファレンス」”, “書籍原稿”, “書籍執筆関連、技術書典など”, “ぴよまるソフトウェア”, “Documents”, “me”, “Users”, “/”}

–Find Book Folder Root which name begins with “–”
set aCount to 1
set hitF to false
repeat with i in bList
  set j to contents of i
  
if j begins with “–” then –”–”ではじまる親フォルダを書籍のルートフォルダとみなす
    set hitF to true
    
exit repeat
  end if
  
set aCount to aCount + 1
end repeat

if hitF = false then
  display dialog “Folder structure error” buttons {“OK”} default button 1 with icon 1
  
return
end if

set cList to items 1 thru ((length of aList) - aCount + 1) of aList
set cStr to (NSString’s pathWithComponents:cList) as string

–Find Image folder in Book root folder
set theFolders to mdLib’s searchFolders:{cStr} searchString:“kMDItemContentType == [c]%@ && kMDItemFSName contains [c]%@” searchArgs:{“public.folder”, “_images”}

set theTargFol to contents of first item of theFolders
–> “/Users/me/Documents/ぴよまるソフトウェア/書籍執筆関連、技術書典など/書籍原稿/–Book 1「AppleScript最新リファレンス」/9999_images”

set newPath to ((NSString’s stringWithString:theTargFol)’s stringByAppendingPathComponent:imgFileName) as string
set newPath2 to chkExistPOSIXpathAndIncrementChildNumber(newPath) of me

–ファイルコピー
set aRes to my copyFileAt:imgPOSIX toFilePath:newPath2

–Markdown書類とコピーした画像ファイルの相対パスを計算する
set relativePath to calcRelativePath(aPOSIXpath, newPath2) of me

set allText to “![” & imgFileName & “](” & relativePath & “)”
–> “![img-2.php.jpeg](../9999_images/img-2.php.jpeg)”

set the clipboard to allText

–POSIX path stringを与えると、ファイル名の重複を検出して、ファイル名の名称回避を行って、ファイル名のみを返す
on chkExistPOSIXpathAndIncrementChildNumber(a)
  set aStr to NSString’s stringWithString:a
  
set bStr to aStr’s lastPathComponent()
  
set cStr to (bStr’s pathExtension()) as string
  
set dStr to (bStr’s stringByDeletingPathExtension()) as string
  
set eStr to (aStr’s stringByDeletingLastPathComponent()) as string
  
  
set aManager to NSFileManager’s defaultManager()
  
set aRes to (aManager’s fileExistsAtPath:aStr) as boolean
  
if aRes = false then return a
  
  
set hitF to false
  
repeat with i from 1 to 65535
    set tmpPath to (eStr & “/” & dStr & “_” & (i as string) & “.” & cStr)
    
set tmpStr to (NSString’s stringWithString:tmpPath)
    
set aRes to (aManager’s fileExistsAtPath:tmpStr) as boolean
    
set bRes to ((tmpStr’s caseInsensitiveCompare:eStr) is not equal to (NSOrderedSame)) as boolean
    
    
if {aRes, bRes} = {false, true} then
      set hitF to true
      
exit repeat
    end if
  end repeat
  
  
if hitF = false then return false
  
  
–ファイルパス(フルパス)からファイル名部分を取得
  
set returnFileName to tmpStr’s lastPathComponent()
  
return (returnFileName as string)
  
end chkExistPOSIXpathAndIncrementChildNumber

–ファイルコピー
on copyFileAt:origPOSIXPath toFilePath:newPOSIXPath
  set POSIXPath1 to NSString’s stringWithString:origPOSIXPath
  
set POSIXPath2 to NSString’s stringWithString:newPOSIXPath
  
set fileManager to NSFileManager’s defaultManager()
  
set theResult to fileManager’s copyItemAtPath:POSIXPath1 toPath:POSIXPath2 |error|:(missing value)
  
return (theResult as integer = 1)
end copyFileAt:toFilePath:

–2つのPOSIX pathの相対パスを計算する
on calcRelativePath(aPOSIXfile, bPOSIXfile)
  set aStr to NSString’s stringWithString:aPOSIXfile
  
set bStr to NSString’s stringWithString:bPOSIXfile
  
  
set aList to aStr’s pathComponents() as list
  
set bList to bStr’s pathComponents() as list
  
  
set aLen to length of aList
  
set bLen to length of bList
  
  
if aLen bLen then
    copy aLen to aMax
  else
    copy bLen to aMax
  end if
  
  
repeat with i from 1 to aMax
    set aTmp to contents of item i of aList
    
set bTmp to contents of item i of bList
    
    
if aTmp is not equal to bTmp then
      exit repeat
    end if
  end repeat
  
  
set bbList to items i thru -1 of bList
  
set aaItem to (length of aList) - i
  
  
set tmpStr to {}
  
repeat with ii from 1 to aaItem
    set the end of tmpStr to “..”
  end repeat
  
  
set allRes to NSString’s pathWithComponents:(tmpStr & bbList)
  
return allRes as text
end calcRelativePath

★Click Here to Open This Script 

2017/11/09 指定フォルダ内のOSAXのSDEFファイルを指定フォルダに書き出す

指定フォルダ内のOSAX(Scripting Additions)のAppleScript用語辞書(sdef)のファイルを指定フォルダに書き出すAppleScriptです。

大昔のClassic Mac OS時代の(68kのバイナリが入っている)OSAXのAppleScript用語辞書の内容を最新のmacOS環境でファイルとして書き出せる、というなかなかに「お前以外に誰が使うんだ?」という内容ですが、割とトンでもない破壊力のあるものです。

sdef1.png

Classic Mac OS時代のScripting Additionsを資料としてとってありますが、これらのAppleScript用語辞書はaeteリソース内に書かれていました(この画面↓はClassic Mac OSエミュレータの「SheepShaver」で起動した漢字Talk 8.6上のスクリプトエディタ。ResEditを探したものの仮想マシン内にみつかりませんでした)。

classic_osax_resized.png

これらの用語辞書は現行のmacOSのスクリプトエディタでもオープンして内容を確認できます。

sdef4_resized.png

さらに、用語辞書のウィンドウ上部のプロキシーアイコンをCommand-クリックすると、テンポラリディレクトリ内に用語辞書(sdef)が書き出されていることを確認できます。

sdef5_resized.png

ということは、この用語辞書(sdef)のパスを求めると、コピーして保管しておくことが可能だということがわかります。

実行してOSAXの入っているフォルダ、書き出しフォルダを指定すると、

sdef2.png

sdefを書き出してくれます。

sdef3_resized.png

ResEdit(死語)でオープンしなくても、sdefなら単なるテキストなので、内容を参照することもかんたんです。

1回実行したら2度目はなさそうな内容なので、ほとんど書き捨てな内容です。途中、スクリプト用語辞書を参照できないOSAXもあったりで、オープン時にエラーダイアログが表示されたりしますが、そこは自分でOKボタンをクリックして回避する必要があります。

AppleScript名:指定フォルダ内のOSAXのSDEFファイルを指定フォルダに書き出す
– Created 2017-11-09 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4961

set a to choose folder with prompt “Original Classic OSAX folder”
set aExportFol to choose folder with prompt “SDEF export folder”

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

repeat with i in aList
  set aRes to saveOSAXsdef(contents of i, aExportFol) of me
  
if aRes = false then
    log i
  end if
end repeat

on saveOSAXsdef(a, aExportFol)
  set aInfo to info for a
  
set aKind to kind of aInfo
  
  
if aKind is in {“スクリプティング機能追加”, “Scripting addition”} then –Scripting Addirions (*Localized*)
    try
      tell application “Script Editor”
        try
          set aDoc to open a
        on error
          return
        end try
        
        
tell front document
          set aPath to path
        end tell
      end tell
    on error
      return false
    end try
    
    
set aExpPOS to (POSIX path of aExportFol)
    
    
–ファイルをコピー
    
set aRes to my copyFileAt:aPath toFolder:aExpPOS
  else
    return false
  end if
  
  
tell application “Script Editor”
    close every document without saving
  end tell
  
return true
end saveOSAXsdef

on copyFileAt:POSIXPath toFolder:folderPath
  set POSIXPath to current application’s NSString’s stringWithString:POSIXPath
  
set folderPOSIXPath to current application’s NSString’s stringWithString:folderPath
  
– build path for new file
  
set theName to POSIXPath’s lastPathComponent()
  
set newPath to folderPOSIXPath’s stringByAppendingPathComponent:theName
  
set fileManager to current application’s NSFileManager’s defaultManager()
  
set theResult to fileManager’s copyItemAtPath:POSIXPath toPath:newPath |error|:(missing value)
  
return (theResult as integer = 1)
end copyFileAt:toFolder:

★Click Here to Open This Script 

2017/11/08 システム環境設定で指定のpane中の指定のanchorを表示する v2

システム環境設定で指定のpane中の指定のanchorを表示するAppleScriptです。

comapplesystempreferences_resized.png

システム環境設定はScriptableなアプリケーションですが、どのpane、どのpane中のanchorを表示するぐらいの機能しかついていません。項目によってはSystem Events経由で設定内容を取得・再設定することもできます。

syspref1_resized.png
▲「一般」「デスクトップとスクリーンセーバ」などの各項目がpane

syspref2_resized.png
▲「ディスプレイ」「配置」などの各項目がanchor。ただし、かならずしも各タブ項目がanchorとして定義されているわけではない

Mac OS X 10.2:ただ辞書がついただけ
Mac OS X 10.4:paneとanchorが取得できるようになった。revealコマンドが追加された
Mac OS X 10.5:print系の機能が強化された

と、Mac OS X 10.4でほぼ機能が確立して、以後あまりAppleScriptから利用できる機能について変更されていません。最新のSystem Preferences.app v14ではauthorizeというユーザー認証(ユーザー名とパスワードの入力)を求める機能が追加されたぐらいです。

syspref_auth.png

authorizeでユーザー認証が成功したかどうか、という結果は返ってきません。ただ、認証のダイアログを表示させるだけのコマンドです。

ただし、システム環境設定のPaneにどのようなものがあり、その中に含まれるanchorにどのようなものがあるかどうかは、OSバージョンごとに異なります。

AppleScript名:システム環境設定で指定のpane中の指定のanchorを表示する v2
– Created 2017-10-10 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4956

set aList to getEveryPaneID() of me
–> “com.apple.preferences.appstore”, “com.apple.preferences.Bluetooth”, “com.nvidia.CUDAPref”….}

set pList to getEveryAnchorName(“com.apple.preference.displays”) of me
–> {”displaysArrangementTab”, “displaysNightShiftTab”, “displaysColorTab”, “displaysGeometryTab”, “displaysDisplayTab”}

set aRes to dispSystemPreferences(“com.apple.preference.displays”, “displaysNightShiftTab”) of me
–> true

on dispSystemPreferences(paneID, anchorID)
  tell application “System Preferences”
    activate
    
set pList to id of every pane
    
if paneID is not in pList then return false
    
    
set current pane to pane paneID
    
tell current pane
      set nList to name of every anchor
      
if anchorID is not in nList then return false
      
reveal anchor anchorID
    end tell
  end tell
  
  
return true
end dispSystemPreferences

on getEveryPaneID()
  tell application “System Preferences”
    return id of every pane
  end tell
end getEveryPaneID

on getEveryAnchorName(paneID)
  tell application “System Preferences”
    set pList to id of every pane
    
if paneID is not in pList then return false
    
set current pane to pane paneID
    
tell current pane
      return name of every anchor
    end tell
  end tell
end getEveryAnchorName

★Click Here to Open This Script 

AppleScript名:システム環境設定の「セキュリティとプライバシー」でユーザー認証
– Created 2017-10-10 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4956

set aRes to dispSystemPreferences(“com.apple.preference.security”, “Privacy_Assistive”) of me

tell application “System Preferences”
  authorize current pane
end tell

on dispSystemPreferences(paneID, anchorID)
  tell application “System Preferences”
    activate
    
set pList to id of every pane
    
if paneID is not in pList then return false
    
    
set current pane to pane paneID
    
tell current pane
      set nList to name of every anchor
      
if anchorID is not in nList then return false
      
reveal anchor anchorID
    end tell
  end tell
  
  
return true
end dispSystemPreferences

★Click Here to Open This Script 

2017/11/07 1D Listから重複項目のみ抽出 v2

1D List(1次元配列)中における重複項目のみをリストアップするAppleScriptです。

Pure AppleScriptでもよく書く内容ですが(書き捨てレベル)、Cocoaの機能を利用するとこんな感じでしょうか。Predicates文で表記できそうな気もするのですが、、、、

AppleScript名:1D Listから重複項目のみ抽出 v2
– Created 2017-11-07 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4955

property NSCountedSet : a reference to current application’s NSCountedSet

set aList to {1, 2, 3, 4, 1, 2, 3, 4, 5, 1, 2, 3, 1, 2, 3, 8, 10, -2}
set aRes to returnDuplicatesOnly(aList) of me
–>  {1, 2, 3, 4}

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/11/06 IBM Watson Language Translator(POST)v1

IBMのWeb Servece「IBM Bluemix」の翻訳機能「Language Translator」を呼び出すAppleScriptです。

IBM Bluemixが無料のお試しコース「ライト・プラン」を11月から開始したので、ためしに使ってみました。

Bluemixの内容はAmazon AWSのようにユーザーが作ったカスタムREST APIをクラウド側にホスティングするもので、所定の固有機能を提供するのは「Watson」と呼ばれるサービス群です。

「Language Translator」はWatsonのうちの1つのサービスで、無償のライト・プランでは1か月あたり100万文字まで翻訳処理可能。有料プランにアップグレードすると、カスタム・モデル(機械学習モデル?)を作成・利用できるとのこと。日本語のテキストと英語のテキストのペアを学習させると、その内容を学習して賢く翻訳してくれることを期待したいところです(そううまくいかないのがこの世の常なわけですが)。

無償のLanguage TranslatorサービスはIBMが用意したデフォルトのモデルを用いて翻訳を行うことになります。撒き餌としては、おいしすぎると無償サービスでユーザーが満足するし、まずすぎると寄り付かなくなるというさじ加減が難しいところ。

Watsonの各種サービスは、各種処理用のカスタムモデルのメンテナンスをユーザーが任意に行えるというのが売りのようで(結局はメンテナンスしきれなくてお任せプランに移行?)、デフォルトのモデルだとそれほど使えないことは想像に難くありません。実際、無償版のLanguage Translatorサービスは、中学生が辞書を片手に翻訳したような文章(個人の見解です)を出力してきました。

BluemixだWatsonだといっても、一般的なRESTful APIであることにはかわりありません。エンドポイントやパラメータを指定してただ呼び出すだけです。ただ、IBMがWeb上に用意しているドキュメントが分かりにくく、必要な情報になかなかたどりつけませんでした(Microsoftのドキュメントの方が数億倍分かりやすかった)。

REST APIとして見て特徴的なのは、Basic認証を併用していること(よそで見かけたことがない)。いつもどおりのREST API呼び出しAppleScriptにBasic認証を追加してみたものの、なかなか認証を通らなかったので、仕方なくcurlコマンドで呼び出してみました(curlコマンドはなるべくやりたくない)。

IBM Bluemixの「ライト・プラン」にサインアップして、Watsonの「Language Translator」サービスをイネーブルにすると発行されるユーザー名とパスワードをretPassword()ハンドラ内に「user:password」の形式で記入しておいて実行すると呼び出せます。

デフォルトのモデルだと日本語から英語の翻訳もへっぽこです。Bluemixの利点を感じるためには、カスタム・モデルが体験できないとダメだと思うのですが、、、、。あと、Web上のドキュメントやサービスにたどり着くための導線がダメすぎです(ーー;; IBM BluemixのWebをすみずみまで目を通したところ、無理矢理に英語を機械翻訳で日本語にしているためか、キーワードの統一(メニュー名とかユーザー名とか)がぜんぜんできておらず、この内容だと英語で使ったほうがまだ楽です。現状だと、豪華で巨大で開けられない箱に入れられた、衣ばかりで身の細いエビフライみたいな、、、、

AppleScript名:Watson Language Translator(POST)v1
– Created 2017-11-05 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4954

set aTargStr to “そして目の前にいるのは神様。少なくとも本人はそう言ってる。神様が言うには、間違って僕を死なせてしまったらしいが、死んだという実感がいまいち自分には無い。”
set aUserInfo to retPassword() of me
set aRes to translateByWatson(aTargStr, “ja”, “en”, aUserInfo) of me
–>  {”And in front of the eyes of Jesus.At least he to say so.God says, seem to have let me die, but dead wrong sense lacking I do not.”}

on translateByWatson(aTargStr, aOrigLang, aTargLang, aUserInfo)
  
  
set aURL to “https://gateway.watsonplatform.net/language-translator/api/v2/translate”
  
  
set shellStr to “curl -X POST –user \”" & aUserInfo & “\” \\
–header \”Content-Type: application/json\” \\
–header \”Accept: application/json\” \\
–data ’{\”text\”:\”"
& aTargStr & “\”,\”source\”:\”" & aOrigLang & “\”,\”target\”:\”" & aTargLang & “\”}’ \\
https://gateway.watsonplatform.net/language-translator/api/v2/translate”

  
  
set aRes to do shell script shellStr
  
  
set jsonString to current application’s NSString’s stringWithString:aRes
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
set tList to (aJsonDict’s translations’s translation) as list
  
return tList
end translateByWatson

on retPassword()
  return “xXXXXxXX-XXxX-XXXx-xXXX-xxxXXXXXxxxX:XXXXxXxXxxxX” –user:pass
end retPassword

★Click Here to Open This Script 

2017/11/05 オープン中のScriptをScptdで保存し直してオープン

Script Editorでオープン中のAppleScript書類をScript Bundle形式で保存して、オープンし直すAppleScriptです。

Script Menuから実行することを目的としたAppleScriptです。

bundlescript_resized.png

 (1)Script Editorの最前面のAppleScript書類の種類を判定
 (2)書類がscptであれば、同じフォルダにscptdとして保存
 (3)元のAppleScript書類をクローズ
 (4)Script Bundle書類をオープン

という動作を行います。(2)のさいにファイル名が衝突しないように、ファイル名末尾に連番をつけて回避しています。

作成中のScriptのバンドル内にファイルを入れたいがscpt形式で作成してしまっていた場合に、その場でバンドル形式に変更するためのツールです。

AppleScript名:オープン中のScriptをScptdで保存し直してオープン
– Created 2017-11-05 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4952

property NSFileManager : a reference to current application’s NSFileManager
property NSString : a reference to current application’s NSString
property NSOrderedSame : a reference to current application’s NSOrderedSame

tell application “Script Editor”
  set dCount to count every document
end tell

if dCount = 0 then
  display dialog “No Document”
  
return
end if

tell application “Script Editor”
  tell front document
    set docPath to path
  end tell
end tell

set newDocPath to determineFileNameWithExternalNumber(docPath, “scpt”, “scptd”) of me
if newDocPath = false then
  display dialog “Script is already script bundle”
  
return
end if

tell application “Script Editor”
  tell front document
    save in (POSIX file newDocPath) as “script bundle”
    
close
  end tell
  
  
open (POSIX file newDocPath) as alias
end tell

–オリジナルのファイルから拡張子を付け替えつつファイル名衝突回避(連番追加)
on determineFileNameWithExternalNumber(docPathPOSIX, oldExt, newExt)
  set curExt to getFileNameExtensionFromFullPath(docPathPOSIX) of me
  
set aManager to NSFileManager’s defaultManager()
  
  
if curExt = oldExt then
    set pathString to NSString’s stringWithString:docPathPOSIX
    
set newPath1 to pathString’s stringByDeletingPathExtension()
    
set newPath2 to newPath1’s stringByAppendingPathExtension:newExt
    
    
set aRes to (aManager’s fileExistsAtPath:newPath2) as boolean
    
set eStr to (pathString’s stringByDeletingLastPathComponent()) as string
    
set bRes to ((newPath2’s caseInsensitiveCompare:eStr) is not equal to (NSOrderedSame)) as boolean
    
    
if {aRes, bRes} = {false, true} then return (newPath2 as string)
    
    
set hitF to false
    
repeat with i from 1 to 65535
      set addingStr to “_” & (i as string) & “.” & newExt
      
set tmpPath to (newPath1’s stringByAppendingString:addingStr)
      
      
set aRes to (aManager’s fileExistsAtPath:tmpPath) as boolean
      
set bRes to ((tmpPath’s caseInsensitiveCompare:eStr) is not equal to (NSOrderedSame)) as boolean
      
if {aRes, bRes} = {false, true} then
        set hitF to true
        
exit repeat
      end if
    end repeat
    
    
if hitF = false then error “File name error”
    
return tmpPath as string
    
  else if curExt = newExt then
    return false
  end if
end determineFileNameWithExternalNumber

–ファイルパスから拡張子を取得する
on getFileNameExtensionFromFullPath(aPOSIXpath)
  set pathString to NSString’s stringWithString:aPOSIXpath
  
return (pathString’s pathExtension()) as string
end getFileNameExtensionFromFullPath

★Click Here to Open This Script 

2017/11/05 1D Listから指定要素のみを削除 v3

1次元配列(1D List)から指定要素を削除するAppleScriptのShane Stanleyによる改修版です。

自分の書いた前バージョンよりも「ちょっと効率的」(Shane談)に短くまとめられています。

とりあえず「こんなんあったらいいよね」「そんなに難しくないよね」というところから書いたものですが、自分でも書きながら非効率な部分がいろいろあるのは(変数内容の型チェック部分)自覚していました(汗)。

string/text/Unicode textはMac OS X 10.5以降では同じものとして扱われているので別々に判定する必要はないし、サブルーチンの宣言文部分ですでに型の強制は行っているので、二重にチェックする必要はありません。

最も注目すべきはPredicate文の書き方です。数値と文字列で場合分けしなくて済むというのは盲点でした(まだ、ちょっとPredicate文に慣れてないかも)。

AppleScript名:指定要素のみを削除(predicateでfilter) v3
– Created 2017-11-03 by Takaaki Naganoya
– Modified 2017-11-05 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4951

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

set aList to {“Apple”, “Lemon”, “Aple”, “Lum”, “Apple”}
set aTarg to “Apple” –string
set rList to removeElementsFrom1DArray(aList, aTarg) of me
–>  {”Lemon”, “Aple”, “Lum”}

set bList to {1, 2, 3, 1, 5}
set bTarg to 1 –number
set rList to removeElementsFrom1DArray(bList, bTarg) of me
–>  {2, 3, 5}

set cList to {1, 2, 3, 1, 5}
set cTarg to {1, 2, 3} –list of number
set rList to removeElementsFrom1DArray(cList, cTarg) of me
–>  {5}

set dList to {“Apple”, “Lemon”, “Aple”, “Lum”, “Apple”}
set dTarg to {“Apple”, “Lemon”} –list of string
set rList to removeElementsFrom1DArray(dList, dTarg) of me
–>  {”Aple”, “Lum”}

on removeElementsFrom1DArray(aList as list, aTarg as {number, string, list})
  set anArray to NSArray’s arrayWithArray:aList
  
  
set aClass to class of aTarg
  
if aClass = list then
    set thePred to NSPredicate’s predicateWithFormat_(“NOT SELF IN %@”, aTarg)
  else
    set thePred to NSPredicate’s predicateWithFormat_(“SELF != %@”, aTarg)
  end if
  
  
set bList to (anArray’s filteredArrayUsingPredicate:thePred) as list
  
  
return bList
end removeElementsFrom1DArray

★Click Here to Open This Script 

2017/11/04 1D Listから指定要素のみを削除 v2

1次元配列(1D List)から指定要素を削除するAppleScriptです。

パラメータ(削除対象)は、数値、文字、数値のlist、文字のlistで指定できます。

ただし、各種アプリケーションのオブジェクトを入れて処理することはできないので、注意が必要です。

AppleScript名:指定要素のみを削除(predicateでfilter) v2
– Created 2017-11-03 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/4950

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

set aList to {“Apple”, “Lemon”, “Aple”, “Lum”, “Apple”}
set aTarg to “Apple” –string
set rList to removeElementsFrom1DArray(aList, aTarg) of me
–>  {”Lemon”, “Aple”, “Lum”}

set bList to {1, 2, 3, 1, 5}
set bTarg to 1 –number
set rList to removeElementsFrom1DArray(bList, bTarg) of me
–>  {2, 3, 5}

set cList to {1, 2, 3, 1, 5}
set cTarg to {1, 2, 3} –list of number
set rList to removeElementsFrom1DArray(cList, cTarg) of me
–>  {5}

set dList to {“Apple”, “Lemon”, “Aple”, “Lum”, “Apple”}
set dTarg to {“Apple”, “Lemon”} –list of string
set rList to removeElementsFrom1DArray(dList, dTarg) of me
–>  {”Aple”, “Lum”}

on removeElementsFrom1DArray(aList as list, aTarg as {number, string, list})
  set anArray to NSArray’s arrayWithArray:aList
  
  
set aClass to class of aTarg
  
if aClass is in {string, text, Unicode text} then
    set aTarg to quoted form of aTarg
    
set thePred to NSPredicate’s predicateWithFormat:(“NOT SELF == “ & (aTarg as string))
  else if aClass is in {integer, number, real} then
    set thePred to NSPredicate’s predicateWithFormat:(“NOT SELF == “ & (aTarg as string))
  else if aClass = list then
    set thePred to NSPredicate’s predicateWithFormat_(“NOT SELF IN %@”, aTarg)
  else
    error “Illegal Parameter”
  end if
  
  
set bList to (anArray’s filteredArrayUsingPredicate:thePred) as list
  
  
return bList
end removeElementsFrom1DArray

★Click Here to Open This Script 

2017/11/03 1D Listから正規表現で抽出

1次元配列(1D List)から正規表現で抽出するAppleScriptです。

Pure AppleScriptだと、list(配列)からデータを抽出する場合には、

 (1)listの要素をループで条件判断して抽出
 (2)データの抽出機能を持つアプリケーションにインポートして抽出(FileMaker ProとかDatabase Eventsとか)

というやり方を用いる必要がありました。だいたいは(1)が使われてきたように(個人的に)感じます。GUIアプリケーションからデータを取得するような場合には、フィルタ参照であらかじめ選択データのしぼりこみを行っておくなどの「通信量、処理データ量を減らす」ための機能を活用するのも常套手段です。

AppleScriptがGUIアプリケーションとの間でやりとりを行うさいに用いられるAppleEventはプロセス間通信の一種であり、割と処理時間のかかる内容であるため、AppleScriptにおいてもGUIアプリケーションとの間のやりとりを減らす(=通信頻度、通信データ量を減らす)ことは処理速度の向上に寄与します。

しかし、macOS 10.10からCocoaの機能をAppleScriptのどのランタイム環境でも使えるようになったので、GUIアプリケーションに依存せずCocoaの機能を用いて、大量の要素を持つlistを(いったんNSArray/NSMutableArrayを経由して)高速に条件抽出できるようになりました。

単純な条件文で抽出できるほか、正規表現でも抽出できます。本サンプルはAppleのPredicate Programming Guide掲載サンプル(Objective-C)をAppleScriptに翻訳したものです。

AppleScript名:1D Listから条件抽出(正規表現1)
– 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/4949

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

set aList to {“TATACCATGGGCCATCATCATCATCATCATCATCATCATCATCACAG”, “CGGGATCCCTATCAAGGCACCTCTTCG”, “CATGCCATGGATACCAACGAGTCCGAAC”, “CAT”, “CATCATCATGTCT”, “DOG”}

set anArray to NSArray’s arrayWithArray:aList

set aPred to NSPredicate’s predicateWithFormat:“SELF MATCHES ’.*(CAT){3,}(?!CA).*’”
set bRes to (anArray’s filteredArrayUsingPredicate:aPred) as list
–>  {”CATCATCATGTCT”}

★Click Here to Open This Script 

AppleScript名:1D Listから条件抽出(正規表現2)
– 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/4949

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

set aList to {“123456789X”, “9876x”, “987654321x”, “1234567890″, “12345X”, “1234567890X”, “999999999X”, “1111111111″, “222222222X”}

set anArray to NSArray’s arrayWithArray:aList

set aPred to NSPredicate’s predicateWithFormat:“SELF MATCHES ’\\\\d{10}|\\\\d{9}[Xx]’”
set bRes to (anArray’s filteredArrayUsingPredicate:aPred) as list
–>  {”123456789X”, “987654321x”, “1234567890″, “999999999X”, “1111111111″, “222222222X”}

★Click Here to Open This Script 

2017/11/02 インストールされているフォントをdisplay nameからキーワード検索 v3

macにインストールされているフォントのdisplay nameからキーワード検索するAppleScriptのShane Stanleyによる高速版です。

  「さすがにFontBook検索より遅いのはダメでしょ」

というツッコミがShane Stanleyからあり、このコードがメールで届いたのでありました。

 FontBook.app:0.6 sec.
 AppleScriptObjc:0.2 sec.

と、このぐらいだと納得できる感じでしょう。

私にはこの、

 NSFontManager’s sharedFontManager()’s availableFonts()

というメソッドを見つけ切れなかったので(一番上に書いてあるのに ーー;)ああいう書き方をしましたが、NSFontManagerからavailableFontFamilies()を経由しないでavailableFonts()で一気に個別のフォント名を取得できれば、条件抽出も割と高速にできるわけです。

AppleScript名:インストールされているフォントをdisplay nameからキーワード検索 v3
– Created 2017-11-01 by Takaaki Naganoya
– Modified 2017-11-02 by Shane Stanley
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
–http://piyocast.com/as/archives/4948

property NSFontManager : a reference to current application’s NSFontManager
property NSFont : a reference to current application’s NSFont
property NSPredicate : a reference to current application’s NSPredicate

set fRes to getEveryFontContainsAQueryStr("Helvetica") of me
–>  {"Helvetica", "Helvetica-Bold", "Helvetica-BoldOblique", "Helvetica-Light", "Helvetica-LightOblique", "Helvetica-Oblique", "HelveticaNeue", "HelveticaNeue-Bold", "HelveticaNeue-BoldItalic", "HelveticaNeue-CondensedBlack", "HelveticaNeue-CondensedBold", "HelveticaNeue-Italic", "HelveticaNeue-Light", "HelveticaNeue-LightItalic", "HelveticaNeue-Medium", "HelveticaNeue-MediumItalic", "HelveticaNeue-Thin", "HelveticaNeue-ThinItalic", "HelveticaNeue-UltraLight", "HelveticaNeue-UltraLightItalic"}

set fRes to getEveryFontContainsAQueryStr("ことり") of me
–>  {"TheLittleBirdFONT", "kotorimojiFONT-TT"}

on getEveryFontContainsAQueryStr(queryName as string)
  set hitFontList to {}
  
set aFontList to NSFontManager’s sharedFontManager()’s availableFonts()
  
– filter out hidden fonts
  
set thePred to NSPredicate’s predicateWithFormat:"NOT SELF BEGINSWITH ’.’"
  
set aFontList to aFontList’s filteredArrayUsingPredicate:thePred
  
  
repeat with fontName in aFontList
    set aFont to (NSFont’s fontWithName:fontName |size|:16)
    
set aDispFontName to (aFont’s displayName()) as string
    
    
if aDispFontName contains queryName then
      set end of hitFontList to (fontName as text)
    end if
  end repeat
  
  
return hitFontList
end getEveryFontContainsAQueryStr

★Click Here to Open This Script