Archive for the '10.10 savvy' Category

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/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

ASCObjC Explorerの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/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/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/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/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/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 

2017/10/29 指定フォルダ以下の指定形式の書類のファイル名重複チェック

指定フォルダ以下の指定形式の書類をすべてもとめ、拡張子をはずしたファイル名に重複がないかチェックするAppleScriptです。

実行にはShane StanleyのAppleScript Libraries「Metadata Lib」(Spotlight検索)を必要とします。

filenames.png

指定フォルダ以下のすべてのファイルを、1つの出力フォルダに同一形式で書き出したような場合に、

 ・もともとは別フォルダに存在していた
 ・ファイル名は同じだが拡張子が違うために重複がわからなかった

ことが理由で、書き出し時にファイル名の衝突が発生するケースが(よく)あります。

その衝突チェックを事前に行う目的で作ったものです。以前にPure AppleScriptで(Cocoa呼び出しがサポートされていない時代に)作ってみたことがありますが、かなりおおがかりになっていました。

AppleScript名:指定フォルダ以下の指定形式の書類をすべてもとめて拡張子をはずしたファイル名に重複がないかチェック v2
– 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/4940

set docUTIList to {“net.daringfireball.markdown”, “com.apple.iwork.pages.sffpages”}
set origFol to (choose folder)
set dRes to detectDocNameDuplicateWithoutExt(origFol, docUTIList) of me
–> true / false

–origFolはaliasでもPOSIX pathでも可
on detectDocNameDuplicateWithoutExt(origFol, docTypeList as list)
  script spdMD
    property allResList : {}
  end script
  
  
set (allResList of spdMD) to {}
  
  
repeat with i in docTypeList
    set j to contents of i
    
set aResList to (mdLib’s searchFolders:{origFol} searchString:(“kMDItemContentTypeTree CONTAINS %@”) searchArgs:{j})
    
if aResList = missing value or aResList = {} then
      –Hitしなかった
    else
      set (allResList of spdMD) to (allResList of spdMD) & aResList
    end if
  end repeat
  
  
set aLen to length of contents of (allResList of spdMD)
  
if aLen = 0 then error “No match”
  
  
set anArray to current application’s NSArray’s arrayWithArray:(allResList of spdMD)
  
set aRes to anArray’s valueForKeyPath:“lastPathComponent.stringByDeletingPathExtension”
  
set b1Res to uniquify1DList(aRes as list, true) of me
  
set b1Len to length of b1Res
  
  
if aLen = b1Len then
    return true – No Duplicates
  else
    return false –Some duplicates
  end if
end detectDocNameDuplicateWithoutExt

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

★Click Here to Open This Script 

2017/10/28 指定アプリの指定メニュー内の指定GUI部品を検索

指定アプリケーションの指定メニュー内の指定GUI部品を検索するAppleScriptです。

GUI Scriptingを使ってアプリケーションのメニュー操作を行うと、ちょっとアプリケーション開発側がメニューの文言や項目を増やしたぐらいで影響を受けて、アプリケーションのバージョンアップやOSのバージョンアップで使えなくなることが多々あります。

GUI Scriptingとは原理上そういうものなので、必要に応じて修正を行ってメンテナンスする必要があります。自分でもXcode上でアプリケーションを作っていると、画面構成やメニュー項目を変更する必要があれば行いますし、実に手軽に行えるので「変更するな」とはとても言えません。

menuitem1_resized.png
▲returnMenuItemRef(アプリケーション名, rootメニュー項目, 検索対象メニュー項目タイトル) of meでPDF書き出しメニュー項目への参照を取得できる

OSやアプリケーション自体のバージョンアップを気にしないでメニュー項目をGUI Scriptingから継続的に呼び出すようにするのは、不可能ではありません。条件を制限すればある程度対応できます。

アプリケーションの名前、メニューの名前、メニュー項目の名前が変わっていなければ、途中のオブジェクト階層などが変更になっても対処は可能です(項目名が変わってしまったら対応はできませんけれども)。

本Scriptは動的にアプリケーションのメニュー項目を全取得し、指定のアプリケーションの指定メニュー内の指定メニュー項目をサーチします。サーチ対象を少なくしてスピードをかせぐために、指定メニュー項目以下のオブジェクトのみを取得しています。

オブジェクトの取得自体はそれほど時間はかからないものの、取得したオブジェクトから目的のものをサーチする(属性値を取得して比較する)のに(オブジェクト数に応じた)時間がかかってしまうため、こうした処理対象のしぼりこみは必須です。

当初、予想よりもパフォーマンスが出なかった(6秒ぐらいかかっていた)のですが、原因がトンでもないところにありました。私の環境では、「最近使った項目」の記憶最大数を拡張してあったのですが、そこに大量に項目が記憶されており、それがパフォーマンスを低下させていました。

recentmenu_resized.png

「最近使った項目」については、メニューから(ファイル履歴を)消去すればパフォーマンスを向上できました(0.7秒程度)。

Scriptの冒頭で必要なメニュー項目への参照をまとめて取得してpropertyにでも登録しておき、Script内で実際にメニュー操作を行う箇所でpropertyに入れておいたメニュー項目を実行すると効果的でしょう(毎回メニュー項目を検索するのは無駄なので)。

menu-item2_resized.png

メニューにはタイトルが設定されていますが、その他のGUI部品にも一意に指し示せるようなユニークなIDとかURLとかが取得できるとよいのですが、、、、(ーー;;

もともと、この手の動的なGUI部品オブジェクト検索はWebブラウザ上の部品検索などで行っており、Webコンテンツ操作で重宝していました。

AppleScript名:指定アプリの指定メニュー内の指定GUI部品を検索
– Created 2017-10-01 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4936

set menuItemRef1 to returnMenuItemRef(“MacDown”, “ファイル”, “PDF…”) of me
–>  menu item “PDF…” of menu “エクスポート” of menu item “エクスポート” of menu “ファイル” of menu bar item “ファイル” of menu bar 1 of application process “MacDown” of application “System Events”

on returnMenuItemRef(appName, menubarMenuItem, lastMenuItemName)
  tell application “System Events”
    tell process appName
      set menuList to name of every menu of menu bar 1
      
set aMenuInd to offsetInList(menubarMenuItem, menuList) of me
    end tell
    
    
set aList to entire contents of (menu bar item aMenuInd of menu bar 1 of process appName)
    
    
repeat with i in aList
      set j to contents of i
      
try
        set aNameVal to value of attribute “AXTitle” of j
        
if (aNameVal = lastMenuItemName) then
          return j
        end if
      end try
    end repeat
  end tell
  
  
return false
end returnMenuItemRef

on offsetInList(aStr, aList)
  set anArray to current application’s NSArray’s arrayWithArray:aList
  
set aInd to (anArray’s indexOfObject:aStr)
  
if aInd = current application’s NSNotFound or (aInd as number) > 9.99999999E+8 then
    error “Invalid Character Error”
  else
    return (aInd as integer) + 1 –0 to 1 based index conversion
  end if
end offsetInList

★Click Here to Open This Script 

2017/10/27 OTMXAttributeでxattrを削除する

オープンソースのプログラム「OTMXAttribute」(By Ryan Coffman)をフレームワーク化したXAttribute.frameworkを呼び出してファイルの拡張属性(xattr)を削除するAppleScriptです。

実際に本Scriptを試すためには、「XAttribute.framework」を~/Library/Frameworksに自己責任でインストールしてください。

–> Download Framework Binary

なにもObjective-Cのプログラムを呼び出さなくても、shell scriptのxattrコマンドを呼び出すだけでもよかったわけですが、あくまで実験です。

用途は、YouTubeからダウンロードした映像ファイルから音声部分のみを抽出する際にXattr属性がついていると、(ランタイム環境によっては)実行が阻害されるようだったので、処理途中でXattr属性を削除することを試してみた、というものです。

さまざまな部品を組み込むことで、Script Menuから呼び出したAppleScriptで、Safariで表示中のYouTubeのムービーをダウンロードして音声部分のみを抽出する処理を呼び出せているので、よいのではないかと。

scriptmenu.png
▲SafariのScript Menuに登録してあるYouTube系Script

AppleScript名:OTMXAttributeでxattrを削除する
– Created 2017-10-27 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “XAttribute” –https://github.com/rylio/OTMXAttribute
–http://piyocast.com/as/archives/4933

set aFile to POSIX path of (choose file)
set xRes to removeXAttrFromFile(aFile, “com.apple.quarantine”)

on removeXAttrFromFile(aFile, anXattr)
  –Get Xattr String
  
set anAttribute to (current application’s OTMXAttribute’s stringAttributeAtPath:aFile |name|:anXattr |error|:(missing value))
  
if anAttribute = missing value then return true –There is no use to remove xattr
  
  
–Remove Xattr
  
set xRes to (current application’s OTMXAttribute’s removeAttributeAtPath:aFile |name|:anXattr |error|:(missing value))
  
if xRes = missing value then return false
  
return (xRes as boolean)
end removeXAttrFromFile

★Click Here to Open This Script 

2017/10/13 指定クラスがどのFrameworkに所属しているか検索

文字列として与えたCocoaのClassがどのFrameworkに所属しているかを検索するAppleScriptです。Spotlight検索のためにShane Stanleyの「Metadata Lib」のインストールを必要とします。

割と切実に欲しかったAppleScriptです。よくShane Stanleyから「そのクラス呼ぶんだったら、このFrameworkをuseで宣言しとかないとダメだぞー」と小突かれているので、「自動でチェックしてえなぁ(涙)」と思っていたところでした。

use文によるFrameworkの使用宣言は、AppleScriptそれ自体で宣言していなくても、Script Editor側で使用宣言しているとそのまま動いてしまったりするパターンがあるため、割と見過ごしてしまう例がありました(Script Editor上でエラーメッセージ出ないし)。

わざわざAppleのWeb Referenceを検索したりと、本質的でない不毛な作業が必要。

そこで、Class名を文字列で与えるとどのFrameworkをuseしないといけないのかを調べる本AppleScriptを書いたわけです。

ながらく「書きたい」と思っていたわけですが、どこを手がかりにすればよいか長らくわかっていませんでした。

それが、macOS 10.13.0のScripting Bridgeのバグに直面して、Scripting Bridgeの「仕組み」そのものについて自分でも調べる機会がありました(正確に把握するため、どこがダメでどこでAppleがミスしたのか追調査)。

すると、macOS内の/System/Library/Frameworksフォルダ内にあるApple純正フレームワークの中に「(フレームワーク名).bridgesupport」という記述ファイルが存在しており、この中にScripting Bridge経由で機能を公開する内容が書いてあることが見て取れました。

macOS 10.13.0では、この.bridgesupportファイルの記述が間違っていた、というのがShane Stanleyの指摘です。

あれ??? この.bridgesupportファイルの中には当該フレームワークが他の言語に対して公開しているクラス名やメソッド名などの一覧が書かれているわけで、この中を検索すれば、目的のクラスを使うにはどのフレームワークをuseコマンドで参照するように宣言すればよいかわかる???? 

完全にもともとの用途とは違う使い道ですが、わかることはわかる(はず)。

冗談半分で書いてみたら、自分の目的を果たすことができました。1回の検索あたり0.7〜1.1秒程度です。結果をキャッシュするとか、検索するごとに毎回読むのをやめるとかすればもっと大幅に高速化はできるものと思われますが、そこまで気合いを入れる内容ではないのでこんな感じでしょうか。

ただし、例外で「CIColor」がみつかりません。ほかにも、この方法で見つけられないものがあるかもしれません。思いつきを形にしたぐらいの内容なので、ミスがあってもご容赦を。

AppleScript名:指定クラスがどのFrameworkに所属しているか検索
– Created 2017-10-13 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/4894

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

set t1Res to searchInBridgeSupport("NSString") of me
–>  {"Foundation.framework"}

set t2Res to searchInBridgeSupport("CLLocation") of me
–>  {"MapKit.framework", "CoreLocation.framework"}

set t3Res to searchInBridgeSupport("NSRect") of me
–>  {"WebKit.framework", "Foundation.framework", "AppKit.framework", "ScreenSaver.framework", "Quartz.framework", "Quartz.framework", "Carbon.framework"}

on searchInBridgeSupport(aKeyword)
  set theFolder to "/System/Library/Frameworks/"
  
set theFiles to mdLib’s searchFolders:{theFolder} searchString:"kMDItemDisplayName ENDSWITH %@" searchArgs:{".bridgesupport"}
  
  
set keyList to {}
  
set the end of keyList to "<class name=’" & aKeyword & "’>"
  
set the end of keyList to aKeyword
  
  
repeat with ii in keyList
    set matchedList to {}
    
set targString to contents of ii
    
    
repeat with i in theFiles
      set j to contents of i
      
      
set aStr to (NSString’s stringWithContentsOfFile:j encoding:NSUTF8StringEncoding |error|:(missing value))
      
set aRange to (aStr’s rangeOfString:targString)
      
      
if aRange’s location() ≠ current application’s NSNotFound and (aRange’s location()) < 9.99999999E+8 then
        if j does not contain "PyObjC" then –ignore PyObjC’s bridge support
          set tmpStr to (current application’s NSString’s stringWithString:j)
          
set pathList to tmpStr’s pathComponents() as list
          
set pathRes to contents of item 5 of pathList
          
set the end of matchedList to pathRes
        end if
      end if
    end repeat
    
    
if length of matchedList > 0 then
      return matchedList
    end if
    
  end repeat
  
  
return {}
end searchInBridgeSupport

★Click Here to Open This Script 

2017/10/12 リストの連結(Cocoa版やや高速版)

リスト(配列)の連結を行うAppleScriptのCocoa版の高速版です。

自分でも実験してCocoa版の実行速度が遅すぎたので不思議に思っていましたが、Shane Stanleyからツッコミがあって、「こう書くと速いよ」と教えてもらいました。

自分の環境で実行してみたところ、0.5 Sec前後。Pure AppleScriptの限界チューニング版(0.069 Sec)よりは10倍ぐらい遅いですが、arrayByAddingObjectsFromArray:で連結したときよりは10倍高速。このぐらいだと(Cocoa呼び出しで余計にかかる処理時間も)納得できる感じでしょうか。

AppleScript名:リストの連結(addObjectsFromArray)
– Created 2017-10-12 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4893

property NSMutableArray : a reference to current application’s NSMutableArray

set anArray to NSMutableArray’s new()

repeat 10000 times
  anArray’s addObjectsFromArray:{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
end repeat

return anArray

★Click Here to Open This Script 

2017/10/11 リストの連結

リスト(配列)の連結を行うAppleScriptのCocoa版です。

1Dリスト(1次元配列)同士の連結を行うのは、Pure AppleScriptだと、

set aList to {}
set bList to {1, 2, 3}
set cList to {4, 5, 6}

set the end of aList to bList
set the end of aList to cList
aList
–> {{1, 2, 3}, {4, 5, 6}}

★Click Here to Open This Script 

こういうやり方と、

set aList to {2, 3, 4, 5, 6}
set bList to {1, 2, 3, 4, 5}

aList & bList
–> {2, 3, 4, 5, 6, 1, 2, 3, 4, 5}

★Click Here to Open This Script 

というやり方があるわけですが、Cocoaの機能を用いたAppleScriptObjCだとリストの要素連結だと、

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

set anArray to current application’s NSMutableArray’s new()
anArray’s addObject:{1, 2, 3}
anArray’s addObject:{4, 5, 6}
anArray as list
–>  {{1, 2, 3}, {4, 5, 6}}

★Click Here to Open This Script 

これを使うことが多かったのですが、リストごと連結するパターンを試していなかったので、調べてみました。

AppleScript名:リストの連結(arrayByAddingObjectsFromArray)
– 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/4892

property NSMutableArray : a reference to current application’s NSMutableArray

set anArray to NSMutableArray’s new()

repeat 10000 times
  set anArray to anArray’s arrayByAddingObjectsFromArray:{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
end repeat

return (anArray as list)
–>  {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10…….}
–4.89sec

★Click Here to Open This Script 

ただし、小さいデータをこまかく連結する程度だとPure AppleScriptの方が速いので、巨大なデータを扱うとかXcode上でCocoa AppleScriptアプレットを作るためにサブルーチン間でCocoaオブジェクトをやりとりするような用途の場合に意識する程度でしょうか。

同じ要素数(10万アイテムの連結)でもPure AppleScriptで限界までチューニングして高速化すると0.069秒で処理終了します。

2017/10/07 なろう小説APIを呼び出す v2(Zip展開つき)

「小説家になろう」サイトのAPI「なろう小説API」を呼び出してデータを取得するAppleScriptです。

「なろう小説API」は事前にAPI Keyの取得も不要で、簡単に呼び出せるのでお手軽に使えます。用意されているのはデータ取得のメソッドであり、結果の保持や集計、しぼりこみについてはローカル側で勝手に行う放任仕様です条件抽出のパラメータもあります。

ただし、本APIが用意しているZip圧縮を利用するのに少々骨が折れました。

http header中でContent-Encodingに「gzip」を指定する程度では対応できませんでした。Zip圧縮転送指定時(パラメータにgzipを指定)APIから返ってくるデータそのものがZip圧縮されており、APIから返ってきたデータそのもの(NSData)を展開する必要がありました。

そこで、NSDataをそのままZip展開できるオープンソースのフレームワーク「GZIP」(By Nick Lockwood)を利用してみました。同プロジェクトはGithub上のXcodeプロジェクトをXcodeでビルドするとFrameworkが得られるので、ビルドして~/Library/FrameworksフォルダにGZIP.frameworkを入れてください。

とりあえず呼び出して500件のデータを取得していますが、500件ずつ取得してループするように書き換えるとよいでしょう。

AppleScript名:なろう小説API v2(Zip展開つき)
– Created 2017-10-03 by Takaaki Naganoya
– 2017 Piyomaru Software
–http://piyocast.com/as/archives/4890
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “GZIP”
–https://github.com/nicklockwood/GZIP
–http://dev.syosetu.com/man/api/
–1日の利用上限は80,000または転送量上限400MByte???

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

set reqURLStr to “http://api.syosetu.com/novelapi/api/” –通常API
–set reqURLStr to “http://api.syosetu.com/novel18api/api/”–18禁API

set aRec to {gzip:“5″, out:“json”, lim:“500″}

set aURL to retURLwithParams(reqURLStr, aRec) of me

set aRes to callRestGETAPIAndParseResults(aURL) of me

set aRESCode to (responseCode of aRes) as integer
if aRESCode is not equal to 200 then return false

set aRESHeader to responseHeader of aRes
set aRESTres to (json of aRes) as list

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  set aRequest to NSMutableURLRequest’s requestWithURL:(|NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setValue:“gzip” forHTTPHeaderField:“Content-Encoding”
  
  
set aRes to NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
  
set rRes to bRes’s gunzippedData() –From GZIP.framework
  
  
set resStr to NSString’s alloc()’s initWithData:rRes encoding:(NSUTF8StringEncoding)
  
  
set jsonString to NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
end callRestGETAPIAndParseResults

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

★Click Here to Open This Script 

2017/10/05 クリップボード内のZero Width Spaceを削除する

クリップボードから、Cocoaの機能を使うと削除も置換もできないZero Width Spaceを削除するAppleScriptです。

zerowidthspace.png
ScriptableなUnicode文字情報チェックツール「UnicodeChecker」

特定のエディタ(ASObjCExplorer)が出力するものの、Cocoaの機能を使うと削除や置換ができないために、Objective-CやSwiftだけで何も対策しないで組んであると編集自体が行えないという恐怖のキャラクターZero Width Space。

各テキストエディタのZero Width Spaceへの対応度はまちまちで、

TextWranler(=BBEdit):表示および削除が可能
tetwrangler.png

CotEditor:ちょっと前まで認識・表示しなかった。最新版(v3.2.2)では表示・削除ができるようになった
coteditor.png

mi:表示できないが、Zero Width Spaceがある場所で不自然にカーソルが進まなくなるので、存在は確認できる。マウスで前後の文字ごとまとめて範囲選択して削除することは可能

といった状況です。実際にCocoaのAPIを用いて文字置換するとぜんぜんダメなので、Pure AppleScriptの機能を用いて組んでいます。

自分がMac App Storeで販売中のPDF差分検出アプリケーション「Double PDF」でもこのZero Width Space対策を行なっており、本文テキスト同士の比較時にはあらかじめ削除するようにしています。

scrit_menu_zero.png

本Scriptは利用頻度が妙に高いので、Script Menuに入れて呼び出すような運用を行っています。クリップボードにZero Width Space駆除対象文字列を入れて(コピーして)本Scriptを実行すると、クリップボード内からZero Width Spaceが駆除されます。

AppleScript名:クリップボード内のZero Width Spaceを削除する
– Created 2017-10-05 by Takaaki Naganoya
– 2017 Piyomaru Software
–http://piyocast.com/as/archives/4881
set aText to the clipboard
set bText to aText as string
set cText to repChar(bText, string id 8203, “”) of me
set the clipboard to (cText as string)

–文字置換
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

★Click Here to Open This Script 

AppleScript名:Zero Width Spaceのプロパティを取得
–http://piyocast.com/as/archives/4881
tell application “UnicodeChecker”
  properties of code point (8203 + 1)
end tell
–> {bidi mirrored:false, containing plane:plane id 0 of application “UnicodeChecker”, id:8203, line break:”ZW”, assigned:true, canonical combining class description:”Not_Reordered”, unicode name:”ZERO WIDTH SPACE”, assigned to abstract character:true, code point type:Format, class:code point, bidi class description:”Boundary_Neutral”, script name:”Common”, general category description:”Format”, bidi class:”BN”, containing block:block “General Punctuation” of application “UnicodeChecker”, general category:”Cf”, name:”", canonical combining class:0}

★Click Here to Open This Script 

2017/09/26 バンドルIDで指定したプロセスを強制終了

Bundle IDで指定したプロセスを強制終了するAppleScriptです。

実際に試してみたらshellのkill/killallコマンドなどと異なり、Finderを強制終了したら終了しっぱなしになりました。

FinderをkillしてFinderがわりのアプリケーションを起動しておく用途のために必要な機能です。

AppleScript名:バンドルIDで指定したプロセスを強制終了(NSRunningApplication)
– Created 2017-09-17 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/4847

set pRes to forceQuitAProcessByBUndleID(“com.apple.finder”) of me

–指定プロセスの強制終了
on forceQuitAProcessByBUndleID(aBundleID)
  set appArray to current application’s NSRunningApplication’s runningApplicationsWithBundleIdentifier:aBundleID
  
if appArray’s |count|() > 0 then
    set appItem to appArray’s objectAtIndex:0
    
set aRes to (appItem’s terminate()) as boolean
    
return aRes
  else
    return false
  end if
end forceQuitAProcessByBUndleID

★Click Here to Open This Script 

2017/09/21 Finder上で指定文字列でSpotlight検索を行なった結果を表示する

FinderのWindowで指定文字列によるSpotlight検索結果を表示するAppleScriptです。

このScriptによるSpotlight検索結果は、あくまでSpotlight結果表示を行うだけのものです。本当にSpotlightによる検索結果を取得したい場合には、大量にサンプルがあるのでそちらを参考にしてください。

たったの1行ですが、Pure AppleScriptだけでは実現できない機能です。何かの実行結果により作成した文字列でSpotlight検索を行い、結果をFinder上で表示するような用途で使えそうです。

spotlight2.png
▲Finderの検索フィールドに検索文字列を入れたのと同じ状態を作ります

spotlight1.png
▲Finder上で指定文字列によるSpotlight検索結果が表示されます

AppleScript名:Finder上で指定文字列でSpotlight検索を行なった結果を表示する
– Created 2017-09-21by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4835

set aResCode to current application’s NSWorkspace’s sharedWorkspace()’s showSearchResultsForQueryString:“Framework”

★Click Here to Open This Script 

2017/09/21 迷路をRTFで作成して脱出経路を赤く着色する v2

2D迷路の作成プログラム「MazeFinder」(By Toby Jennings)をフレームワーク化したものを呼び出して、文字ベースの迷路データをRTFに変換して脱出経路情報を着色してデスクトップに書き出すAppleScriptのアップデート版です。

maze1.png

処理結果はとくに前バージョンと変化ありませんし、実行時間については前バージョンよりも0.02秒ほど余計にかかるようになりました。

では、なぜ書き換えたかといえば、各機能の再利用性の向上が目的です。せっかく書いておいても、他の用途に転用するのに手間がかかるようであれば意味がありません。さまざまな用途に転用しやすい構造(サブルーチンとしてまとめているとか)に書いておくことはとても重要です。

再利用したい機能モジュールをよりサブルーチン化するように書き換えました。こうしておくことで、まったく違った用途にもすぐに転用できます。

本Scriptを試す場合には、MazeFinder.frameworkをダウンロードして~/Library/Frameworksフォルダに入れてください。

–> Download Framework Binary

60×60の迷路を作成して着色してファイル書き出しを終了するまでに、開発環境で0.1秒程度かかりました(前バージョンは0.08秒ぐらい)。

AppleScript名:迷路をRTFで作成して脱出経路を赤く着色する v2
– Created 2017-09-19 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use framework “MazeFinder” –https://github.com/tcjennings/MazeFinder
–http://piyocast.com/as/archives/4834

property NSColor : a reference to current application’s NSColor
property Board2D : a reference to current application’s Board2D
property NSUUID : a reference to current application’s NSUUID
property NSDictionary : a reference to current application’s NSDictionary
property NSMutableArray : a reference to current application’s NSMutableArray
property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString
property NSFontAttributeName : a reference to current application’s NSFontAttributeName
property NSFont : a reference to current application’s NSFont
property NSString : a reference to current application’s NSString
property NSDocumentTypeDocumentAttribute : a reference to current application’s NSDocumentTypeDocumentAttribute
property NSForegroundColorAttributeName : a reference to current application’s NSForegroundColorAttributeName
property Pathfinder : a reference to current application’s Pathfinder
property NSLiteralSearch : a reference to current application’s NSLiteralSearch
–NSNotFoundはプロパティに代入しても認識されなかった

set targFontName to “Courier-Bold” –PostScript Name
set targFontSize to 13 –Point

–迷路テキストデータ作成→Attributed Stringに
set aStr to create2DMazeAndSolveIt(30, 30, 1, 1, 28, 28) of me –Plain Text
set anAttr to changeAttrStrsFontAttribute(aStr, targFontName, targFontSize) of me –Attributed String

–迷路データに着色(参照呼び出し, Call by reference)
markCharOfAttributedString(anAttr, aStr, “!”, (NSColor’s redColor())) of me

–結果のRTFをデスクトップ上に書き出す。ファイル名はUUID.rtf
set targFol to current application’s NSHomeDirectory()’s stringByAppendingPathComponent:“Desktop”
set aRes to my saveStyledTextAsRTF(targFol, anAttr)

–指定のAttributed String内で指定文字列が含まれる箇所に指定の色をつける(結果はメイン側に参照渡し)
on markCharOfAttributedString(anAttr, origStr, aTargStr, aColor)
  set rList to searchWordWithRange(origStr, aTargStr) of me
  
repeat with ii in rList
    (anAttr’s addAttribute:(NSForegroundColorAttributeName) value:aColor range:ii)
  end repeat
end markCharOfAttributedString

–指定の文字列をAttributed Stringに変換して任意のフォントを一括指定
on changeAttrStrsFontAttribute(aStr, aFontPSName, aFontSize)
  set tmpAttr to NSMutableAttributedString’s alloc()’s initWithString:aStr
  
set aRange to current application’s NSMakeRange(0, tmpAttr’s |length|())
  
set aVal1 to NSFont’s fontWithName:aFontPSName |size|:aFontSize
  
tmpAttr’s beginEditing()
  
tmpAttr’s addAttribute:(NSFontAttributeName) value:aVal1 range:aRange
  
tmpAttr’s endEditing()
  
return tmpAttr
end changeAttrStrsFontAttribute

–指定テキストデータ(atargText)内に、指定文字列(aSearchStr)が含まれる範囲情報(NSRange)をすべて取得する
on searchWordWithRange(aTargText, aSearchStr)
  set aStr to NSString’s stringWithString:aTargText
  
set bStr to NSString’s stringWithString:aSearchStr
  
set hitArray to NSMutableArray’s alloc()’s init()
  
set cNum to (aStr’s |length|()) as integer
  
  
set aRange to current application’s NSMakeRange(0, cNum)
  
  
set aCount to 1
  
repeat
    set detectedRange to aStr’s rangeOfString:bStr options:NSLiteralSearch range:aRange
    
if detectedRange’s location is equal to (current application’s NSNotFound) then exit repeat
    
    
hitArray’s addObject:detectedRange
    
    
set aNum to (detectedRange’s location) as integer
    
set bNum to (detectedRange’s |length|) as integer
    
    
set aRange to current application’s NSMakeRange(aNum + bNum, cNum - (aNum + bNum))
    
set aCount to aCount + 1
  end repeat
  
  
return hitArray
end searchWordWithRange

–スタイル付きテキストを指定フォルダ(POSIX path)にRTFで書き出し
on saveStyledTextAsRTF(targFol, aStyledString)
  set bstyledLength to aStyledString’s |string|()’s |length|()
  
set bDict to NSDictionary’s dictionaryWithObject:“NSRTFTextDocumentType” forKey:(NSDocumentTypeDocumentAttribute)
  
set bRTF to aStyledString’s RTFFromRange:(current application’s NSMakeRange(0, bstyledLength)) documentAttributes:bDict
  
  
set theName to (NSUUID’s UUID()’s UUIDString())
  
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

–2D迷路を作成して脱出経路を検索して文字列で迷路データを出力する
–迷路サイズ x,y スタート座標 x, y ゴール座標 x,y
on create2DMazeAndSolveIt(xMax, yMax, xStart, yStart, xGoal, yGoal)
  set myBoard to Board2D’s alloc()’s init()
  
myBoard’s setupBoardWithRows:xMax WithColumns:yMax –60×60ぐらいが上限。正方形でないとダメ
  
myBoard’s createMazeFromBoard()
  
  
set myPathfinder to Pathfinder’s alloc()’s init()
  
set myPath to myPathfinder’s findPathThroughMaze:myBoard fromX:xStart fromY:yStart toX:xGoal toY:yGoal
  
  
set aCount to myPath’s |count|()
  
if aCount > 0 then
    set aRes to myBoard’s drawPathThroughMaze:myPath
    
return aRes as string
  else
    return false
  end if
end create2DMazeAndSolveIt

★Click Here to Open This Script 

2017/09/19 Notesで選択中のnoteやfolderの情報を取得する?

defaultsコマンド経由でNotes.app(日本語ローカライズ名:メモ.app)で選択中のnoteやfolderの情報を取得する(参考になるかもしれない)AppleScriptです。

selectionとかselected folderなどの用語が用意されていないことでおなじみのNotes.app。「現在表示中のノート」を処理したいのはやまやまですが、用語辞書に書かれていない機能は、まっとうな手段では呼び出せません。

となると、「まっとうでない方法」を検討することになります。

まっとうでない手段を選ぶことで、スピード低下、安定性のなさ、メンテナンスの手間など負の側面と向き合う必要が出てきますが、問題自体の解決は行えます。

(1)GUI Scripting
まっとうでない方法の筆頭。画面上から無理やり情報を取得して、選択状態にあるGUI部品のタイトルを取得することで、選択中のノートの情報を特定できそうです。興味と必要性がないので深追いしていませんが、Notes.appの各種表示状態(アカウント情報一覧を表示するとか、表示を隠すとか)に合わせて数パターンの処理を用意しておくことで、実現できるメドは立っています。

ただし、GUI Scriptingのつねとして、「スピードは通常のAppleScriptの100倍以上遅い」(Cocoa経由の処理と比較すると数万倍遅い)「予期しないアプリケーション側の表示には追従できない」「OSやアプリケーションのバージョンアップにともない、表示内容やGUI部品の階層が変わったぐらいで書き換えが必要になる」ということから、多用は避けたいところです。

(2)defaultsコマンド
まっとうでない方法として有名。アプリケーションの設定情報を直接読んで処理します。アプリケーションやOSがバージョンアップして内部情報が変更されると動かなくなるのはGUI Scripting同様。

defaultsコマンドから得られる情報をAppleScriptネイティブのデータ形式(recordとか)に変換するのが面倒でしたが、Cocoaの機能を利用して割と手軽に読み込めるようになってきました。

Terminal上で、

  defaults read com.apple.Notes

を実行してみたところ、設定情報の中に現在選択中のnoteおよびfolderを記録している箇所があったので、実際にAppleScript中に記述して取り出してみました。

今回掲載したのが、そのdefaultsコマンドを利用した情報取得のサンプルです。このデータをもとにSQLiteのデータベースを検索してみればいいかも、などと思ってデータベースを漁ってみたものの、

notes_sqlite3_resized.png

defaultsから得られたような形式のID(UUIDっぽい?)はDB中に記録されていないようで、ちょっと残念です(SQLiteではなくCoreData経由で調べるべき?)。もう少し調べてみると手がかりが見つかるかもしれませんが、出先の電車の中で調べた程度だったのでこのぐらいです。

AppleScript名:Notesで選択中のnoteやfolderの情報をdefaults経由で取得する
– Created 2016-10-31 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4827

set sRes to do shell script ” defaults read com.apple.Notes ArchivedUIState”
set aDict to deserializeToPlistString(sRes) of me

set curNote to (aDict’s valueForKey:“currentNote”) as string
–>  ”860327F4-4E6F-44FB-92EC-720B47CDD38A”

set curNoteFol to (aDict’s valueForKey:“currentNoteFolder”) as string
–>  ”184daff620aca4ad23fd2bf70d0b76af”

set folderListHidden to (aDict’s valueForKey:“folderListHidden”) as integer as boolean
–>  false

return {curNote, curNoteFol, folderListHidden}

–XML-format plist string–> list or record
on deserializeToPlistString(aStr as string)
  set deStr to current application’s NSString’s stringWithString:aStr
  
set theData to deStr’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aList to current application’s NSPropertyListSerialization’s propertyListWithData:theData options:(current application’s NSPropertyListMutableContainersAndLeaves) |format|:(missing value) |error|:(missing value)
  
return aList
end deserializeToPlistString

★Click Here to Open This Script 

2017/09/15 Path Finderの最前面のウィンドウで選択中のアイテムをaliasのリストで返す

Finder代替アプリケーション「Path Finder」で最前面のウィンドウので選択中のファイル情報をaliasのlistで取得するAppleScriptです。

Cocoa Finderのパフォーマンスの低さに愛想が尽きたので、Finder代替ツールを調べていたら、Path Finder一択っぽいのでScript対応機能を調べてみたものです。

AppleScript名:Path Finderの最前面のウィンドウで選択中のアイテムをaliasのリストで返す
– Created 2017-09-15 by Takaaki Naganoya
– 2017 Piyomaru Software
–http://piyocast.com/as/archives/4826

set aList to getSelectionFromFrontmostPathFinderWindow() of me
–> {alias “Macintosh HD:Users:me:Documents:AppleScript:各種アプリケーション、Application Specific:InDesign CS3:InDesign CS3で、ドキュメント内にXML要素を作成して、移動.scpt”, alias “Macintosh HD:Users:me:Documents:AppleScript:各種アプリケーション、Application Specific:InDesign CS3:InDesign CS3で、ドキュメント内にXML要素を作成して、複製.scpt”, alias “Macintosh HD:Users:me:Documents:AppleScript:各種アプリケーション、Application Specific:InDesign CS3:InDesign CS3で、ドキュメント内のXML要素を削除.scpt”, alias “Macintosh HD:Users:me:Documents:AppleScript:各種アプリケーション、Application Specific:InDesign CS3:InDesign CS3で、指定ScriptLabelの中のテキストの、指定行の指定文字以降を行末まで取得.scpt”}

on getSelectionFromFrontmostPathFinderWindow()
  tell application “Path Finder”
    set wCount to count every finder window
    
if wCount = 0 then return false
    
    
set aSel to selection
    
if aSel = missing value then return false
    
    
set newList to {}
    
repeat with i in aSel
      set j to (path of i) as alias
      
set the end of newList to j
    end repeat
  end tell
  
return newList
end getSelectionFromFrontmostPathFinderWindow

★Click Here to Open This Script 

2017/09/13 Safariの最前面のウィンドウの内容をすべて取得してTextEditで新規ドキュメントを作成

Safariで表示中の最前面のウィンドウのURLを取得して本文文字列を抽出し、テキストエディットの新規書類としてオープンするAppleScriptです。

たまに、Blog上の情報を部分的に利用したいようなときに、テキスト内容をコピー&ペーストで別のアプリケーションに持ってきますが、たまにテキストの選択ができないBlogに遭遇することがあります。

こんなとき、SafariのAppleScript用語辞書にはdocumentからtextが取得できるということになっていますが、目下Safari 10.1.2上のこの機能にはアクセスできません。SafariのAppleScript用語辞書はSafari 10.xになってからdocumentのpropertiesが取得できない(エラーになる)など、いまひとつです。

さらに、もっと前からdocumentのtextが取得できないようになっているので、とりあえずバグレポートで文句を書きつつ、対処してみました(Appleがバグ修正するのを待つよりも自前でScript書いた方が早いという)。

Safariの最前面のウィンドウ(frontmost document)から表示中のURLだけ取得してくれば、AppleScript単体でWeb内容のダウンロードからテキストの抽出までできるので、そのとおりに処理。取得しただけだとスクリプトエディタ上でログ表示(コピー可)するだけなので、とりあえずテキストエディットの新規書類を作成してその本文にテキストを入れてみました。

ちなみに、本Blog掲載のプログラムリストの末尾に「スクリプトエディタに内容を転送するリンク」をつけていますが、これはコピー&ペーストではなく、カスタムURLプロトコル(applescript://)経由でプロセス間通信によりWebブラウザからスクリプトエディタにデータ転送する仕組みを利用しています。一般ユーザーにとっては、「コピー&ペーストではない」という点がなかなか理解できないようです。

AppleScript名:Safariの最前面のウィンドウの内容をすべて取得してTextEditで新規ドキュメントを作成
– Created 2017-09-09 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/4823

property NSAttributedString : a reference to current application’s NSAttributedString
property NSData : a reference to current application’s NSData
property |NSURL| : a reference to current application’s |NSURL|

–Get URL from frontmost window (document)
tell application “Safari”
  if (every document) = {} then return
  
tell front document
    set aURLstr to URL
  end tell
end tell

–Get String from Safari URL
set sRes to getStringFromRemoteHTML(aURLstr) of me

–Make new document with TextEdit to browse or edit text contents
tell application “TextEdit”
  make new document with properties {text:sRes}
  
activate
end tell

on getStringFromRemoteHTML(aURLstr as string)
  set theURL to |NSURL|’s URLWithString:aURLstr
  
set theData to NSData’s dataWithContentsOfURL:theURL
  
set attStr to NSAttributedString’s alloc()’s initWithHTML:theData documentAttributes:(missing value)
  
if attStr = missing value then error “Internet Connection lost or Wrong URL”
  
return (attStr’s |string|()) as string
end getStringFromRemoteHTML

★Click Here to Open This Script 

2017/09/10 HTMLをplain textに変換 v2

HTMLのファイルをplain textに変換するAppleScriptです。

前バージョンでは、,△蕕じめHTMLを変数に読み込んでおいて、△修離如璽燭NSString経由でNSAttributedStringに(文字エンコーディングを指定しつつ)読み込んでいました。

このため、,涼奮で文字コードエンコーディングの判定がうまく行かないと、正しいPlain Textが得られないという「弱点」がありました(一応、対策のためにテキストエディタ並みの日本語テキストエンコーディング自動判定ライブラリを併用していますが、毎回掲載するわけにもいきません)。

これに対してShane Stanleyから「こうしたほうが」というツッコミがあったのが本Scriptです。HTMLファイルのファイルパスをNSDataに渡して読み込む、▲如璽燭NSAttaributedStringに読み込む という処理に変更。これまで読み込みに問題のあった(テキストエンコーディング判定失敗)HTMLも正しく読み込めているようです。

AppleScript名:HTMLをplain textに変換 v2
– Created 2017-09-09 by Takaaki Naganoya
– Created 2017-09-09 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4821

property NSAttributedString : a reference to current application’s NSAttributedString
property NSData : a reference to current application’s NSData

set aFile to choose file
set aPlainText to HTMLDecode(POSIX path of aFile) of me

on HTMLDecode(thePath)
  set theData to NSData’s dataWithContentsOfFile:thePath
  
set attStr to NSAttributedString’s alloc()’s initWithHTML:theData documentAttributes:(missing value)
  
return (attStr’s |string|()) as string
end HTMLDecode

★Click Here to Open This Script 

2017/09/08 HTMLをplain textに変換(UTF8)

HTMLのファイルをplain textに変換するAppleScriptです。

HTMLのplain text化とくに日本語テキスト入りのHTMLのPlain Text化は昔(Mac OS X 10.4ぐらいの時代)はひじょうに厄介な処理でした。それが、Cocoaの機能を使うと割と簡単にできるようになり、AppleScriptによる処理の「死角」が少なくなってきた印象があります。

HTMLのテキストエンコーディングについては、ヘッダーに書かれている文字コードと実際の文字コードが異なる可能性もあるため(ありがち)、いまのところテキストのエンコーディング自動判別ルーチンを併用しています。ただ、もうちょっと簡潔に処理できないかとも思うところです(2パスでHTMLを読み込んで、1パス目ではヘッダー部分のみ読み取ってエンコーディング情報を取得、2パス目で取得したエンコーディング情報に基づいて全体を読み直し、とか?)。

本ルーチンは、掲載のためにとりあえずHTMLがUTF-8で書かれているという前提にもとづいて処理を行なっています。

→ 改修版(v2)はこちら

AppleScript名:HTMLをplain textに変換(UTF8)
– Created 2017-09-08 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/4818

property NSUnicodeStringEncoding : a reference to current application’s NSUnicodeStringEncoding
property NSAttributedString : a reference to current application’s NSAttributedString
property NSString : a reference to current application’s NSString

set aFile to choose file
set aRes to read aFile as «class utf8» –文字エンコーディング自動判定処理を行なったほうがよい
set aPlainText to HTMLDecode(aRes) of me

on HTMLDecode(HTMLString)
  set theString to NSString’s stringWithString:HTMLString
  
set theData to theString’s dataUsingEncoding:(NSUnicodeStringEncoding)
  
set attStr to NSAttributedString’s alloc()’s initWithHTML:theData documentAttributes:(missing value)
  
return (attStr’s |string|()) as string
end HTMLDecode

★Click Here to Open This Script 

2017/09/06 郵便番号から住所を求める

アイビスのWeb APIを呼び出して(日本国内の)郵便番号から住所を求めるAppleScriptです。

とくにユーザー登録などを行わずにAPIを呼び出せます。ただし、実行結果に半角カタカナを使っているなど、とても不思議な仕様なので、適宜半角→全角変換を行う必要があるものと思われます(21世紀に入って20年近くになるのに、前世紀の遺物である「半角カタカナ」を見ることになるとは、、、)。

いろいろテスト実行してみると、夜間に停止していたり(AWSで動かしているようですが)するように見えます。

郵便番号から住所を検索するサービスといえば、XML-RPC経由での問い合わせが可能な「郵便専門ネット」が存在しています。
→ 郵便専門ネットでXML-RPC経由で郵便番号から住所を返す

さすがに「平成の大合併」といわれた、ひとときの市町村統合の動きは鎮静化していますが、郵便番号はビル単位での指定が可能なので逆に言えばビルの建て替えや統合などによる影響をつねに受け続ける状態であるともいえます。こまめに最新データにキャッチアップしてくれるサービスを選んで使いたいところです。

AppleScript名:郵便番号から住所を求める
– Created 2016-03-06 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://www.ibsnet.co.jp/solution/zipcloud.html
–http://piyocast.com/as/archives/4810

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

set aCode to “176-0022″
set aRec to retAddressFromZipCode(aCode) of me
–>  {kana3:”コウヤマ”, address1:”東京都”, zipcode:”1760022″, kana2:”ネリマク”, prefcode:”13″, address3:”向山”, kana1:”トウキョウト”, address2:”練馬区”}
set aStr to (address1 of aRec) & (address2 of aRec) & (address3 of aRec)
–>  ”東京都練馬区向山”

on retAddressFromZipCode(aCode)
  set reqURLStr to “http://zipcloud.ibsnet.co.jp/api/search”
  
set aRec to {zipcode:aCode}
  
set bURL to retURLwithParams(reqURLStr, aRec) of me
  
set aRes to callRestGETAPI(bURL) of me
  
set aRESTres to json of aRes
  
set aRESCode to responseCode of aRes
  
set aRESHeader to responseHeader of aRes
  
return results of aRESTres as record
end retAddressFromZipCode

–GET methodのREST APIを呼ぶ(パラメータをあらかじめURLに入れておくタイプ)
on callRestGETAPI(aURL)
  –Request
  
set aRequest to NSMutableURLRequest’s requestWithURL:(|NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“GET”
  
aRequest’s setTimeoutInterval:60
  
set aRes to NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
  
–Parse Results
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to NSString’s alloc()’s initWithData:bRes encoding:(NSUTF8StringEncoding)
  
  
set jsonString to NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(NSUTF8StringEncoding)
  
set aJsonDict to NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
set resCode to (dRes’s statusCode()) as integer
  
  
–Get Response Header
  
set resHeaders to (dRes’s allHeaderFields()) as record
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
end callRestGETAPI

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

★Click Here to Open This Script 

2017/09/06 ツイ4のページで新規連載マンガの画像を取得してPDF化(新規連載のPDF化)v3

Safariで表示中のWebマンガサイト「ツイ4」(更新情報をTwitterに投稿)のマンガを全エピソードダウンロードしてPDFにまとめるAppleScriptです。

実行にあたってはShane StanleyのAppleScript Libraries「BridgePlus」のインストールを必要とします(~/ibrary/Script Librariesフォルダに入れるだけ)。

実行開始時にはSafariでツイ4の特定のマンガのページをオープンしている必要があります。

tui4.png

Safariの最前面のウィンドウからURLやTitle、リンクされている画像の詳細情報を取得し、条件チェックなどを行なったのちに詳細なデータの抽出を行います。

次に、PDFの保存先を選択するダイアログを表示。このさい、デフォルトの保存先を「ピクチャ」フォルダ、ファイル名をマンガのタイトルに指定。

ページにリンクされていた画像(ツイではファイル名はシーケンシャル番号)から番号の情報だけを抽出して最大値、最小値を計算。この範囲で画像のダウンロード、PDFへの追記を行います。ただし、実運用してみたところ、Safariからすべての画像を取得できないようで(非同期表示しているようなので)、とりあえず1〜9999までの番号の画像を順次ダウンロードし、画像が存在しなければ処理を終了しています。

画像をダウンロードするたびにPDFに追記していますが、このあたりは途中でエラーが出て停止してもそれまでの処理内容が保存されることを意図してのことです。SSD搭載機では問題のない処理ですが、HDD搭載機では若干遅く感じるかもしれません(もはやHDD搭載機が身の回りにないので不明)。

これまでは、マンガの新規連載がはじまるとcurlコマンドで画像をダウンロードしてPDFに連結する作業を手で行なっていたのですが(誰も頼んでねえよ)、新規連載が増えたので自動化してみました。それでもありあわせの部品を組み合わせただけなので、それほど手間はかかっていません。

本Scriptとは別に更新された差分をPDFに連結するAppleScriptを作って日々実行し、大きな画面でブラウズするのに役立てています。割とこういう、ごくごく私的なScriptで野心的な処理を先行してテストしているものです。

AppleScript名:ツイ4のページで新規連載マンガの画像を取得してPDF化(新規連載のPDF化)v3
– Created 2016-09-05 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use framework “QuartzCore”
use BridgePlus : script “BridgePlus”
–http://piyocast.com/as/archives/4808

property SMSForder : a reference to current application’s SMSForder
property |NSURL| : a reference to current application’s |NSURL|
property NSURLRequest : a reference to current application’s NSURLRequest
property NSURLConnection : a reference to current application’s NSURLConnection
property NSArray : a reference to current application’s NSArray
property NSFileManager : a reference to current application’s NSFileManager
property NSNumberFormatter : a reference to current application’s NSNumberFormatter
property NSPredicate : a reference to current application’s NSPredicate
property PDFPage : a reference to current application’s PDFPage
property PDFDocument : a reference to current application’s PDFDocument
property NSURLRequestUseProtocolCachePolicy : a reference to current application’s NSURLRequestUseProtocolCachePolicy
property NSNumberFormatterPadBeforePrefix : a reference to current application’s NSNumberFormatterPadBeforePrefix
property NSImage : a reference to current application’s NSImage
property NSSortDescriptor : a reference to current application’s NSSortDescriptor
property NSNumber : a reference to current application’s NSNumber
property NSRegularExpressionDotMatchesLineSeparators : a reference to current application’s NSRegularExpressionDotMatchesLineSeparators
property NSRegularExpressionAnchorsMatchLines : a reference to current application’s NSRegularExpressionAnchorsMatchLines
property NSRegularExpression : a reference to current application’s NSRegularExpression
property NSString : a reference to current application’s NSString

property theTargetSite : “http://sai-zen-sen.jp/”

tell application “Safari”
  if (count every document) = 0 then
    display notification “Safari does not open web page”
    
return
  end if
  
  
set docTitle to (do JavaScript “document.title” in front document) –Title
  
  
tell front document –URL
    set aURL to URL
  end tell
end tell

if aURL does not start with theTargetSite then
  display notification “This site is not the target”
  
return
end if

–Safariの最前面のウィンドウから画像リンクをすべて取得(Height, Width, URL)
set aList to getImageSizeAndURLOfFrontSafariDocument() of me

–取得した画像情報の2D Listをサイズで降順ソート
load framework –Force loading BridgePlus framework
set sortIndexes to {0, 1} –Key Item id: begin from 0
set sortOrders to {false, false}
set sortTypes to {“compare:”, “compare:”}
set resList to (current application’s SMSForder’s subarraysIn:(aList) sortedByIndexes:sortIndexes ascending:sortOrders sortTypes:sortTypes |error|:(missing value))

–画像が取得できなかったら処理終了
if (resList as list) = {} then
  display notification “There is no images on this page”
  
return –No Result
end if

–最大サイズの画像情報を取得する(おそらくマンガ)
set {maxHeight, maxWidth, maxURL} to contents of first item of (resList as list)

set aNSURL to |NSURL|’s URLWithString:maxURL
set aNSURLfilename to (aNSURL’s lastPathComponent())
set aNSURLpure to aNSURL’s URLByDeletingLastPathComponent()
set aNSURLextension to aNSURLfilename’s pathExtension() as string
set aNSURLfilenameLen to (aNSURLfilename’s stringByDeletingPathExtension())’s |length|() as integer –画像ファイル名から拡張子を除去した部分の文字列長

–画像情報リストを画像サイズで抽出
set maxHeightStr to (maxHeight as integer) as string
set maxWidthStr to (maxWidth as integer) as string
set thePred to NSPredicate’s predicateWithFormat:(“(self[0] == “ & maxHeightStr & “) AND (self[1] == “ & maxWidthStr & “)”)
set bArray to (resList’s filteredArrayUsingPredicate:thePred) as list

–URLからファイル名の数値部分のみ抽出
set imageArray to current application’s NSMutableArray’s new()
repeat with i in bArray
  set j to contents of last item of i –(Image URL)
  
set aTmpURL to (|NSURL|’s URLWithString:j)
  
set aTmpfilename to (aTmpURL’s lastPathComponent()) as string
  
set numStr to first item of (my findPattern:(“^\\d{1,” & (aNSURLfilenameLen as string) & “}”) inString:aTmpfilename)
  
set jj2 to (SMSForder’s transformedFrom:numStr ICUTransform:“Fullwidth-Halfwidth” inverse:false) as integer
  (
imageArray’s addObject:jj2)
end repeat

–ファイル名から抽出した数値の最小値と最大値を求める。ただ、実運用したらWeb側から画像をすべて取得されない(非同期読み込みを行なっているらしい)ケースがあったため、ここの値は参考値程度にしか使えなかった
set maxRes to (imageArray’s valueForKeyPath:“@max.self”)’s intValue() –最大値
set minRes to (imageArray’s valueForKeyPath:“@min.self”)’s intValue() –最小値
log {minRes, maxRes}

–PDFのファイル名と場所をユーザーに確認
set pdfFile to (choose file name with prompt “Select PDF Name & Location” default location (path to pictures folder) default name (docTitle & “.pdf”))
set pdfFilePOSIX to POSIX path of pdfFile
set newFilePath to current application’s NSString’s stringWithString:pdfFilePOSIX

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

–Download each image and append to blank PDF
set insCount to 1 –画像ダウンロード用のページ数(Loop Counter)とPDF連結用のページ番号(insCount)を分離

–repeat with i from minRes as integer to maxRes as integer
repeat with i from 1 to 9999
  –URL部品の連結
  
set aFILENAME to numToZeroPaddingStr(i, aNSURLfilenameLen, “0″) of me
  
set aFULLURL to (aNSURLpure’s absoluteString() as string) & (aFILENAME as string) & “.” & (aNSURLextension as string)
  
set aURL to (|NSURL|’s URLWithString:aFULLURL)
  
  
–URL(画像)をダウンロード
  
set {uRes, headerRes, aData} to checkURLResourceExistence(aURL, 3) of me
  
  
if uRes = true then
    display notification “Episode “ & (i as string) & ” exists…”
    
set bImage to (NSImage’s alloc()’s initWithData:aData)
    (
aPDFdoc’s insertPage:(PDFPage’s alloc()’s initWithImage:bImage) atIndex:(insCount - 1))
    (
aPDFdoc’s writeToFile:newFilePath) –1Page更新するたびにファイル保存
    
set changedF to true –PDFにページが追記されたことを検出
  else
    display notification “No more new episode….”
    
exit repeat
  end if
  
  
set insCount to insCount + 1
end repeat

–FinderコメントにURLを記入
tell application “Finder”
  set comment of (pdfFile as alias) to (aNSURLpure’s absoluteString() as string)
end tell

–生成したPDFをオープン。ビューワー経由ではなくFinder経由でopen命令を送って表示
tell application “Finder”
  open (pdfFile as alias)
end tell
–ここで処理終了

—————

on getImageSizeAndURLOfFrontSafariDocument()
  set aList to {}
  
  
tell application “Safari”
    if its running then
      if (count every document) = 0 then return {}
      
set aRes to (do JavaScript “document.images.length” in front document)
      
      
repeat with i from 0 to (aRes - 1)
        set aHeight to do JavaScript ((“document.images[” & i as string) & “].height”) in front document
        
set aWidth to do JavaScript ((“document.images[” & i as string) & “].width”) in front document
        
set aSRC to do JavaScript ((“document.images[” & i as string) & “].src”) in front document
        
set the end of aList to {aHeight, aWidth, aSRC}
      end repeat
    end if
  end tell
  
  
return aList
end getImageSizeAndURLOfFrontSafariDocument

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

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

–整数の値に指定桁数ゼロパディングして文字列で返す
on numToZeroPaddingStr(aNum as integer, aDigit as integer, paddingChar as text)
  set aNumForm to NSNumberFormatter’s alloc()’s init()
  
aNumForm’s setPaddingPosition:(NSNumberFormatterPadBeforePrefix)
  
aNumForm’s setPaddingCharacter:paddingChar
  
aNumForm’s setMinimumIntegerDigits:aDigit
  
  
set bNum to NSNumber’s numberWithInt:aNum
  
set aStr to aNumForm’s stringFromNumber:bNum
  
  
return aStr as text
end numToZeroPaddingStr

– 指定URLにファイル(画像など)が存在するかチェック
–> {存在確認結果(boolean), レスポンスヘッダー(NSDictionary), データ(NSData)}
on checkURLResourceExistence(aURL, timeOutSec as real)
  set aRequest to (NSURLRequest’s requestWithURL:aURL cachePolicy:(NSURLRequestUseProtocolCachePolicy) timeoutInterval:timeOutSec)
  
set aRes to (NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value))
  
set dRes to (first item of (aRes as list))
  
set bRes to (second item of (aRes as list))
  
if bRes is not equal to missing value then
    set hRes to (bRes’s allHeaderFields())
    
set aResCode to (bRes’s statusCode()) as integer
  else
    set hRes to {}
    
set aResCode to 404
  end if
  
return {(aResCode = 200), hRes, dRes}
end checkURLResourceExistence

–指定PDFのページ数をかぞえる(10.9対応。普通にPDFpageから取得)
–返り値:PDFファイルのページ数(整数値)
on pdfPageCount(aFile)
  set aFile to POSIX path of aFile
  
set theURL to |NSURL|’s fileURLWithPath:aFile
  
set aPDFdoc to PDFDocument’s alloc()’s initWithURL:theURL
  
set aRes to aPDFdoc’s pageCount()
  
return aRes as integer
end pdfPageCount

★Click Here to Open This Script 

2017/09/05 japaneseTokenizeのじっけん3

Objective-Cで記述した日本語形態素解析フレームワーク「japaneseTokenize」のアップデート版を呼び出し、テキストを文章単位にparseするAppleScriptです。

AppleScriptネイティブの予約語にも「paragraphs of」というものがあり、テキストを改行コード単位で分割してリスト(配列)にして返してくれます。ただ、これだと用途が限定されるので文章単位でparseするメソッドをFrameworkに追加してみました。

# 言葉の意味的に「paragraphs of」ではなく「sentences of」(そんなものはない)に該当する挙動なので、修正して後日掲載

本Scriptを試す場合には、最新版のjapaneseTokenize.framework(v1.1)をダウンロードして~/Library/Frameworksフォルダに入れてください。以前のバージョンのフレームワークがあったら削除してください。

–> Download Framework Binary

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

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

set targString to “これら2つの形態素解析機能のparseの結果が矛盾しているかどうかは未確認ですか? 「今日のばんごはんに何を作ろうか?」 短い文章では同じことを確認してありますが、長い文章でも同じかどうかは未確認。多分同じだとは思いますけれども。”

set aRes to current application’s jTokenize’s parseToParagraphs:targString
set bList to (aRes’s valueForKeyPath:“token”) as list
–> {”これら2つの形態素解析機能のparseの結果が矛盾しているかどうかは未確認ですか? “, “「今日のばんごはんに何を作ろうか?」 ”, “短い文章では同じことを確認してありますが、長い文章でも同じかどうかは未確認。”, “多分同じだとは思いますけれども。”}

★Click Here to Open This Script