Archive for the 'NSData' Category

2017/05/04 Analyze Imageで画像認識

MicrosoftのCognitive ServicesのREST APIのひとつ、Computer Vision APIの「Analyze Image」を呼び出して画像認識するAppleScriptです。

Microsoft Cognitive Servicesのサイトで、開発者アカウント登録を行えば、本APIについては1か月に5,000回まで無料で使用できます(1分間に20回までという制限もあります)。

アカウント登録を行い、Analyze ImageをEnableに設定して、本AppleScriptの末尾のretAPIKey()ハンドラ内にAPI Key 1を入力してください(API Keyを記入したScriptを決してそのまま第三者に配布しないでください。あくまでテストと確認用です)。

指定した画像のカテゴリ(Categories)、タグ情報(Tags)、詳細内容(Description)の情報を取得しています。

精度については、実際にテスト画像と得られた認識データを見比べていただくしかありません。カタツムリとか誕生日ケーキとか夕日や花火、ワインボトルなど(!)、驚くほど正確に認識されています。夕日の手前にあるのは飛行機ではないのですが、そのぐらいの誤差はあります(イルカを「犬」と判定されたりもしました)。

ためしに、この結果(Description)をそのままGoogle Tlanstlate APIに渡して日本語訳させ画像のFinderコメント欄に入れる実験などもやってみたところ、面白い結果が得られました(実用性はまだまだですが、言いたいことはわかる、というレベル。個人的には英語のままのほうがわかりやすいかも)。

こうしたサービスを利用して、写真.app(Photos.app)内の写真にすべてタグ付けして、タグをもとに風景の写真だけを新規アルバムに入れて分類するといった使い方はできそうです(すでにやってるし)。そして、AppleのアプリケーションとMicrosoftやGoogleのWebサービスを連携させるといった使い方はAppleが提供するわけがないので、こういう用途こそAppleScriptで勝手に連携させるべきだと思います。

r0010704_resized.png
{{metadata:{width:2592, |format|:”Jpeg”, height:1944}, tags:{{|name|:”fireworks”, confidence:0.998994648457}, {|name|:”outdoor object”, confidence:0.998748064041}}, categories:{{|name|:”dark_fireworks”, score:0.99609375}}, |description|:{tags:{”fireworks”, “object”}, captions:{{|text|:“a close up of fireworks”, confidence:0.233880494749}}}, requestId:”ce5e5c15-f68f-4647-9694-5fb8ede9cd31″}}

r0015213_resized.png
{{metadata:{width:2592, |format|:”Jpeg”, height:1944}, tags:{{|name|:”ground”, confidence:0.999917149544}, {|name|:”animal”, confidence:0.993541717529}, {|name|:”invertebrate”, hint:”animal”, confidence:0.952201843262}, {|name|:”outdoor”, confidence:0.887740492821}, {|name|:”mollusk”, hint:”animal”, confidence:0.814713895321}, {|name|:”snail”, hint:”animal”, confidence:0.649717211723}}, categories:{{|name|:”abstract_texture”, score:0.6015625}}, |description|:{tags:{”animal”, “outdoor”, “shellfish”, “snail”, “piece”, “laying”, “food”, “sitting”, “top”, “surface”, “lying”, “banana”, “close”, “fruit”, “bird”, “beach”, “plate”, “board”, “street”}, captions:{{|text|:“a snail on the ground”, confidence:0.480042590526}}}, requestId:”b290dc83-0e33-41b0-8a39-76c067bcffb8″}}

r0015918_resized.png
{{metadata:{width:2048, |format|:”Jpeg”, height:1536}, tags:{{|name|:”grass”, confidence:0.99923813343}, {|name|:”outdoor”, confidence:0.997150361538}, {|name|:”ground”, confidence:0.972813665867}, {|name|:”standing”, confidence:0.93672734499}, {|name|:”animal”, confidence:0.917161226273}, {|name|:”bird”, confidence:0.824012756348}}, categories:{{|name|:”animal_horse”, score:0.98046875}}, |description|:{tags:{”grass”, “outdoor”, “standing”, “bird”, “animal”, “water”, “field”, “white”, “small”, “walking”, “front”, “sitting”, “parrot”, “brown”, “large”, “grassy”, “green”, “body”, “dirt”, “red”, “river”}, captions:{{|text|:“a bird that is standing in the grass”, confidence:0.859258793309}}}, requestId:”bc5b8778-cf96-4d3c-8ba9-be22b38c36dd”}}

img_2502_resized.png
{{metadata:{width:3264, |format|:”Jpeg”, height:2448}, tags:{{|name|:”sky”, confidence:0.999066531658}, {|name|:”outdoor”, confidence:0.99786490202}, {|name|:”sunset”, confidence:0.880310297012}, {|name|:”distance”, confidence:0.229086950421}}, categories:{{|name|:”outdoor_”, score:0.0078125}, {|name|:”outdoor_waterside”, score:0.5234375}}, |description|:{tags:{”outdoor”, “sunset”, “airplane”, “plane”, “sitting”, “building”, “runway”, “large”, “front”, “water”, “pier”, “top”, “sun”, “track”, “orange”, “light”, “road”, “bridge”, “standing”, “train”, “man”, “jet”, “white”, “city”, “riding”, “red”, “flying”, “bird”, “blue”, “night”, “river”, “tower”, “tarmac”}, captions:{{|text|:“a plane that is in front of a sunset”, confidence:0.493561860724}}}, requestId:”49705b7d-99cb-4234-9af6-b30a32f02ad5″}}

img_0313_resized.png
{{metadata:{width:4032, |format|:”Jpeg”, height:3024}, tags:{{|name|:”indoor”, confidence:0.929384291172}, {|name|:”candle”, confidence:0.877458691597}, {|name|:”birthday”, confidence:0.817751348019}, {|name|:”lit”, confidence:0.53784763813}}, categories:{{|name|:”others_”, score:0.01171875}}, |description|:{tags:{”table”, “indoor”, “cake”, “birthday”, “food”, “sitting”, “lit”, “plate”, “front”, “small”, “top”, “bowl”, “fruit”, “filled”, “chocolate”, “holding”, “man”, “woman”}, captions:{{|text|:“a birthday cake with lit candles”, confidence:0.8700279282}}}, requestId:”62b58a19-2d3a-4f9a-bd54-5aa4460d8385″}}

img_2187_resized.png
{{metadata:{width:3264, format:”Jpeg”, height:2448}, tags:{{name:”bottle”, confidence:0.999316096306}, {name:”table”, confidence:0.987863183022}, {name:”indoor”, confidence:0.968602716923}, {name:”wine”, confidence:0.928377449512}, {name:”alcohol”, confidence:0.846504926682}, {name:”food”, confidence:0.785823404789}, {name:”glass”, confidence:0.782037138939}, {name:”beverage”, confidence:0.776733756065}}, categories:{{name:”drink_”, score:0.90234375}}, description:{tags:{”bottle”, “table”, “indoor”, “wine”, “sitting”, “alcohol”, “food”, “glass”, “beverage”, “cup”, “banana”, “empty”, “top”, “sandwich”, “counter”, “beer”, “phone”, “orange”, “wooden”, “computer”, “laying”}, captions:{{text:“a bottle of wine”, confidence:0.858091829408}}}, requestId:”d2db3a05-fcb0-43bb-8191-89707904918a”}}

r0020016_resized.png
{{metadata:{width:1280, format:”Jpeg”, height:960}, tags:{{name:”sky”, confidence:0.999883055687}, {name:”outdoor”, confidence:0.997511506081}, {name:”person”, confidence:0.910886585712}, {name:”toy”, confidence:0.878364562988}}, categories:{{name:”others_”, score:0.00390625}, {name:”outdoor_”, score:0.01171875}}, description:{tags:{”outdoor”, “person”, “toy”, “thing”, “man”, “white”, “grass”, “standing”, “people”, “field”, “riding”, “jumping”, “doing”, “air”, “holding”, “group”, “trick”, “board”, “statue”, “dog”, “player”, “ramp”}, captions:{{text:“a statue of a man jumping in the air”, confidence:0.153422169506}}}, requestId:”ec5dc466-76c5-47df-b858-2e418ce891a8″}}

r0016164_resized.png
{{metadata:{width:2048, format:”Jpeg”, height:1536}, tags:{{name:”water”, confidence:0.999519586563}, {name:”animal”, confidence:0.993490874767}, {name:”outdoor”, confidence:0.992024421692}, {name:”aquatic mammal”, hint:”animal”, confidence:0.978799343109}, {name:”mammal”, hint:”animal”, confidence:0.914148926735}, {name:”ocean”, confidence:0.804374277592}, {name:”whale”, hint:”animal”, confidence:0.533892810345}, {name:”wave”, confidence:0.527211129665}, {name:”dolphin”, hint:”animal”, confidence:0.340564578772}}, categories:{{name:”outdoor_”, score:0.00390625}}, description:{tags:{”water”, “animal”, “outdoor”, “mammal”, “ocean”, “wave”, “laying”, “brown”, “riding”, “surfing”, “top”, “body”, “lying”, “board”, “large”, “beach”, “floating”, “swimming”, “standing”, “dog”, “young”, “white”, “man”}, captions:{{text:“a dog swimming in the ocean”, confidence:0.604725984086}}}, requestId:”161b6c0f-f3fd-4e06-b4be-d5baa9516e53″}}

AppleScript名:Analyze Image APIで画像認識
– Created 2017-05-03 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4625

set imgFilePath to POSIX path of (choose file of type {“public.jpeg”} with prompt “Select an Image to Analyze”)
set aRes to recogImage(imgFilePath) of me

on recogImage(imgFilePath)
  set reqURLStr to “https://api.projectoxford.ai/vision/v1.0/analyze”
  
set myAPIkey to retAPIkey() of me –API Key 1
  
  
set aRec to {visualFeatures:“Categories,Tags,Description”, details:“Celebrities”, language:“en”}
  
set bURL to retURLwithParams(reqURLStr, aRec) of me
  
  
set aRes to callRestPOSTAPIAndParseResultsWithImage(bURL, imgFilePath, myAPIkey) of me
  
set aRESTres to json of aRes
  
set aRESCode to responseCode of aRes
  
set aRESHeader to responseHeader of aRes
  
  
return (aRESTres as list)
end recogImage

–POST methodのREST APIを画像をアップロードしつつ呼ぶ
on callRestPOSTAPIAndParseResultsWithImage(aURL, imgFilePath, myAPIkey)
  –Get Image Contents from file
  
set imgPathStr to current application’s NSString’s stringWithString:imgFilePath
  
set imgData to current application’s NSData’s dataWithContentsOfFile:imgPathStr
  
set postBody to current application’s NSMutableData’s |data|()
  
postBody’s appendData:imgData
  
  
–Request
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“POST”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
aRequest’s setHTTPBody:postBody
  
aRequest’s setValue:“application/octet-stream” forHTTPHeaderField:“Content-Type”
  
aRequest’s setValue:myAPIkey forHTTPHeaderField:“Ocp-Apim-Subscription-Key”
  
  
–CALL REST API
  
set aRes to current application’s 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 current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
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)
  
  
–Get Response Code
  
set dRes to contents of second item of resList
  
if dRes = missing value then
    set resCode to -1
    
set resHeaders to {}
  else
    set resCode to (dRes’s statusCode()) as integer
    
–Get Response Header
    
set resHeaders to (dRes’s allHeaderFields()) as record
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
  
end callRestPOSTAPIAndParseResultsWithImage

on retURLwithParams(aBaseURL, aRec)
  set aDic to current application’s 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 (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString())
  
  
return aURL
end retURLwithParams

on retAPIkey()
  return “xXXxXXXXxXXxXXXxXXXXXXXXxXXXXXXx” –API Key 1
end retAPIkey

★Click Here to Open This Script 

2017/04/30 Computer Vision APIで文字認識(OCR)

MicrosoftのCognitive ServicesのREST APIのひとつ、Computer Vision APIを呼び出すAppleScriptです。

MicrosoftのAPIは割とシンプルでオンラインドキュメントも読みやすいため、書くのに3分ぐらいしかかかっていません(既存の別のAPI用のものをend pointとAPI keyとパラメータを書き換えただけ)。

Microsoft Cognitive Servicesのサイトで、開発者アカウント登録を行えば、本APIについては1か月に5,000回まで無料で使用できます(1分間に20回までという制限もあります)。アカウント登録を行い、OCR ServiceをEnableに設定して、本AppleScriptの末尾のretAPIKey()ハンドラ内にAPI Key 1を入力してください(API Keyを記入したScriptを決してそのまま第三者に配布しないでください。あくまでテストと確認用です)。

cognitive_key.png
▲ここの「Key 1」

Vision APIでは、対応画像ファイル形式はJPEG、PNG、GIF、BMP(!)。ファイル容量は4MB以下、50×50ピクセル以上の大きさである必要があります。

img_0320_resized.png

こんな「戦場の絆」のリプレイIDをゲームセンターで撮影して、このIDをもとにYouTubeのリプレイムービーのURLを検索することになるわけですが、このリプレイIDのOCRにComputer Vision APIを使ってみました。

この用途(ランダムに近い英数字の固定桁の組み合わせ、画面上のイメージの認識)については、かなり使える印象です。認識用に使用した画像は、iPhone 7で撮影して写真.appにiCloud経由で転送された写真をそのまま利用しており、ファイルサイズは2.6MB、画像の大きさは4032×3024ピクセルありました。このぐらいだと、認識のためにMicrosoftのクラウドにアップロードするのに少々待たされます。

{boundingBox:”929,1171,746,134″, text:”fy57nt2f“}
{boundingBox:”920,1438,770,135″, text:”3kt5y72v“}

この用途においては、とても誤認識が少ないと感じています。本来取り出したい目的のデータが日本語(ja)ではないので、言語を英数字(en)に指定して呼び出しています。

縦書きの日本語の印刷物の認識だと、もう少し結果が異なってくると思います。一応、この画面についても当初は日本語(ja)で認識していましたが、書体の問題もあり(ちょっと太すぎ?)日本語の文字については誤認識されまくっていました。

AppleScript名:Computer Vision APIで文字認識(OCR)
– Created 2017-04-30 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4622

set imgFilePath to POSIX path of (choose file of type {“public.image”} with prompt “Select an Image to OCR”)

set reqURLStr to “https://api.projectoxford.ai/vision/v1.0/ocr” –Microsoft Cognitive Services OCR endpoint
set myAPIkey to retAPIkey() of me –API Primary Key

–Request parameters
set aRec to {|language|:“en”, detectOrientation:“true”}
set bURL to retURLwithParams(reqURLStr, aRec) of me

set aRes to callRestPOSTAPIAndParseResultsWithImage(bURL, myAPIkey, imgFilePath) of me
set aRESTres to json of aRes

set aRESCode to responseCode of aRes
if aRESCode is not equal to 200 then return false
set aRESHeader to responseHeader of aRes

return ((regions of aRESTres) as list)
–>  {{language:”en”, regions:{{boundingBox:”728,366,1497,2042″, lines:{{boundingBox:”1655,366,186,28″, words:{{boundingBox:”1655,366,186,28″, text:”pea-a-r”}}}, {boundingBox:”1223,962,467,89″, words:{{boundingBox:”1223,962,467,89″, text:”UäLCOD”}}}, {boundingBox:”753,1134,922,171″, words:{{boundingBox:”753,1134,64,156″, text:”1″}, {boundingBox:”929,1171,746,134″, text:”fy57nt2f”}}}, {boundingBox:”1761,1167,458,64″, words:{{boundingBox:”1761,1175,80,53″, text:”20″}, {boundingBox:”1862,1169,169,62″, text:”17/0″}, {boundingBox:”2045,1167,174,60″, text:”4/26″}}}, {boundingBox:”2005,1231,215,52″, words:{{boundingBox:”2005,1231,215,52″, text:”17:55″}}}, {boundingBox:”742,1403,948,170″, words:{{boundingBox:”742,1403,86,160″, text:”2″}, {boundingBox:”920,1438,770,135″, text:”3kt5y72v”}}}, {boundingBox:”1765,1432,459,65″, words:{{boundingBox:”1765,1441,81,53″, text:”20″}, {boundingBox:”1866,1434,170,63″, text:”17/0″}, {boundingBox:”2050,1432,174,61″, text:”4/26″}}}, {boundingBox:”2010,1495,215,54″, words:{{boundingBox:”2010,1495,215,54″, text:”17:47″}}}, {boundingBox:”736,1672,88,176″, words:{{boundingBox:”736,1672,88,176″, text:”3″}}}, {boundingBox:”728,2240,96,168″, words:{{boundingBox:”728,2240,96,168″, text:”5″}}}}}, {boundingBox:”2817,1217,186,320″, lines:{{boundingBox:”2817,1217,175,53″, words:{{boundingBox:”2817,1217,175,53″, text:”6vs6″}}}, {boundingBox:”2826,1483,177,54″, words:{{boundingBox:”2826,1483,177,54″, text:”6vs6″}}}}}}, textAngle:0.0, orientation:”Up”}}

–POST methodのREST APIを画像をアップロードしつつ呼ぶ
on callRestPOSTAPIAndParseResultsWithImage(aURL, anAPIkey, imgFilePath)
  
  
–Get Image Contents from file
  
set imgPathStr to current application’s NSString’s stringWithString:imgFilePath
  
set imgData to current application’s NSData’s dataWithContentsOfFile:imgPathStr
  
set postBody to current application’s NSMutableData’s |data|()
  
postBody’s appendData:imgData
  
  
–Request
  
set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:“POST”
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
aRequest’s setHTTPBody:postBody
  
aRequest’s setValue:“application/octet-stream” forHTTPHeaderField:“Content-Type”
  
aRequest’s setValue:anAPIkey forHTTPHeaderField:“Ocp-Apim-Subscription-Key”
  
  
–CALL REST API
  
set aRes to current application’s 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 current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
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)
  
  
–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 callRestPOSTAPIAndParseResultsWithImage

on retURLwithParams(aBaseURL, aRec)
  set aDic to current application’s 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 (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) –as text
  
  
return aURL
end retURLwithParams

on retAPIkey()
  return “xXXXXxXxXxXXXXxxxXXxXXXxxXxxXxxx” –API Primary Key
end retAPIkey

★Click Here to Open This Script 

2017/02/21 NSDataからMD5値を計算する

NSDataからMD5のdigest値を計算するAppleScriptです。オープンソースのプロジェクト「NSData-MD5」(By Francis Chong)を利用しています

MD5のdigest計算はたいていファイルからshell commandなどで求めるのが一般的な処理ですが、いったんファイル化しなくてはならないので、扱いに困るケースもあります。

そこで、MD5をデータ(変数に入れたデータ)から直接求めるObjective-Cのプログラム(NSData+MD5)を見つけて、Cocoa Framework化し、AppleScriptから呼べるようにしてみました。

テストのためには本フレームワーク(本当にただXcodeのプロジェクトを作ってObjective-Cのプログラムを放り込んだだけ)を~/Library/Frameworks/フォルダに入れて、本AppleScriptを実行してみてください(自己責任でおためしください)。

→ Download Framework

データをMD5 digest化して保持しておけると、大容量データの扱いがとても便利(容量を取らない。相互に照合はできる)なので、この手の機能は欠かせません。ファイルを経由せずに計算したいと考えるのもとてもまっとうな話で、Googleで検索してもよく実装例を見かけます。

MD5値の計算のためにはNSDataに変換する必要があり、手元ではNSStringとNSImage(bitmap化してNSData化)については計算できています。共感していただける人が少ないことはわかっていますが、自分的にはかなり重要な機能です(^ー^;;。

AppleScript名:NSDataからMD5値を計算する
– Created 2016-02-11 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “md5FromDataKit” –https://github.com/siuying/NSData-MD5
–http://piyocast.com/as/archives/4466

set aStr to “ぴよまるソフトウェア”
set aNSStr to current application’s NSString’s stringWithString:aStr
set aData to aNSStr’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
set aMD5Hex to (current application’s NSData’s MD5HexDigest:aData) as string
–>  ”2d0b4e205f274f20b17dc8ca4870f1db”

set aMD5 to (current application’s NSData’s MD5Digest:aData)
–>  (NSData) <2d0b4e20 5f274f20 b17dc8ca 4870f1db>

★Click Here to Open This Script 

2017/01/23 Mail.appで選択中のメッセージからソースを取得してMailCoreで再組み立てして解釈

Mail.appに対してメール(message)の各種属性を問い合わせるのと、Mail.appからメール(message)のソースを取得して、オープンソースのフレームワーク「mailcore2」で各種属性を取得するのとでどちらが速いかを確認するための実験です。

実行のためにはMailCore.frameworkをビルドして、~/Library/Frameworksに入れておく必要があります。

だいたい同じぐらいの属性値を取り出してみたところ、MacBook Pro Retina 2012モデル(Core i7 2.66GHz)でmailcore2経由の処理のほうが1.8倍ぐらい高速(10回実行時の平均値)なことがわかりました。

漫然とAppleScriptからMailの属性値を取ると0.011秒、mailcore2経由だと0.006秒でした。1通のメールでこのぐらいなので、数十、数百通と処理を行うと差が開く可能性はあります。

ただし、普通にMail.appからメールの属性値を取り出す場合にはpropertiesでまとめて取得するのが賢いやりかたなので、そこまで露骨に処理速度の差は出ないかもしれません。

それでも、mailcore2でしか取得できない情報が割といろいろあるので、mailcore2を使っての処理も悪くない感じです(意外でした)。Mailのメッセージのソースをmailcore2で処理できるかどうかの実験だったのですが、速度もなかなか速かったので速度比較をしてみたものです。

AppleScript名:Mail.appで選択中のメッセージからソースを取得してMailCoreで再組み立てして解釈
– Created 2017-01-23 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “MailCore” –https://github.com/MailCore/mailcore2
–http://piyocast.com/as/archives/4406

tell application “Mail”
  set a to selection
  
if a = {} then error “No Selection”
  
set aa to first item of a
  
set aSource to source of aa –メールのソースを取得
end tell

set aStr to current application’s NSString’s stringWithString:aSource
set aData to aStr’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)

set aHeader to current application’s MCOMessageHeader’s headerWithData:aData
set aT1 to (aHeader’s subject()) as string
–> “attachment test”
set aT2 to (aHeader’s bcc())
–>  missing value
set aT3 to (aHeader’s cc())
–>  missing value
set aT4 to (aHeader’s |date|()) as date
–>  date “2017年1月20日金曜日 16:07:14″
set aT5 to (aHeader’s |from|())
–>  (MCOAddress) mailcore::Address:0×600000637ee0 Takaaki Naganoya
set aT6 to (aHeader’s inReplyTo())
–>  missing value
set aT7 to (aHeader’s receivedDate())
–>  missing value
set aT8 to (aHeader’s |references|())
–>  missing value
set aT9 to (aHeader’s replyTo())
–>  missing value
set aT10 to (aHeader’s sender())
–>  missing value
set aT11 to (aHeader’s |to|())
–>  (NSArray) {(MCOAddress) mailcore::Address:0×618000a37460 長野谷隆昌 }
set aT12 to (aHeader’s userAgent()) as string
–>  ”Apple Mail (2.3259)”
set aT13 to (aHeader’s allExtraHeadersNames()) as list
–>  {”Return-Path”, “Content-Type”, “Delivered-To”, “X-Virus-Status”, “X-Mailer”, “Received”, “Mime-Version”}
set aT14 to (aHeader’s extraHeaderValueForName:“X-Mailer”) as string
–>  ”Apple Mail (2.3259)”
set anAddress to (aHeader’s |from|())
–>  (MCOAddress) mailcore::Address:0×6180006393c0 Takaaki Naganoya
set anAdrName to (anAddress’s displayName()) as string
–>  ”Takaaki Naganoya”
set anAdrName to (anAddress’s mailbox()) as string
–>  ”maro_ml@piyocast.com”

★Click Here to Open This Script 

AppleScript名:Mail.appで選択中のメッセージから各種属性値を取得する
– Created 2017-01-23 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4406

tell application “Mail”
  set a to selection
  
if a = {} then error “No Selection”
  
set aa to first item of a
  
  
set aSub to subject of aa
  
set aBCC to bcc recipient of aa
  
set aCC to cc recipient of aa
  
set aRecL to to recipient of aa
  
set aRep to reply to of aa
  
set aRecD to date received of aa
  
set aSenD to date sent of aa
  
set aSender to sender of aa
  
log aSender
  
–User AgentはHeaderから自力で取得
  
set aHead to all headers of aa
  
–displayNameはsenderから文字列処理で自力で
  
–mailboxはsenderから文字列処理で自力 or
end tell

★Click Here to Open This Script 

2017/01/20 MailCore2でメールのeml形式ファイルから添付ファイルを抽出

オープンソースのフレームワーク「MailCore2」を使ってeml形式のメールファイルを読み込んで添付ファイルを取り出すAppleScriptです。

実行のためにはMailCore.frameworkをビルドして、~/Library/Frameworksに入れておく必要があります。

普通にMail.appをGUIなりAppleScriptから操作すれば、もっと手軽にメールの添付ファイルは取得できるわけですが、本Scriptは「うっかり書き出してしまったemlファイル」からの添付ファイル(Zip圧縮したファイルのみ)の抽出を行います。

macOSのMail.appから「添付ファイルつきのメッセージ」をFinderにドラッグ&ドロップして作成したeml形式のファイルが処理対象です。

この処理対象ファイルを本AppleScriptで処理すると、デスクトップフォルダに添付ファイルが保存されます。

MIMEがマルチパートであることは認識していましたが、入れ子状態の階層が割と多い、多階層の入れ子にできるようなので、本来であれば再帰処理すべきなのかもしれません。また、Mail.app以外のメーラーから来たメールはMIMEの組み立て方が異なっているかもしれません。あくまで、macOS 10.12上のMail.app v10.2同士でやりとりした添付ファイルつきメールの処理が本Scriptで行えた、ということにすぎません。

AppleScript名:MailCore2でメールのeml形式ファイルから添付ファイルを抽出
– Created 2017-01-18 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “MailCore” –https://github.com/MailCore/mailcore2
—http://piyocast.com/as/archives/4399

set aPath to POSIX path of (choose file of type {“com.apple.mail.email”})

set fileContents to current application’s NSData’s dataWithContentsOfFile:aPath options:(current application’s NSDataReadingMappedIfSafe) |error|:(missing value)
if fileContents = missing value then return

set aParser to current application’s MCOMessageParser’s messageParserWithData:fileContents

set aBodySet to aParser’s mainPart()
set pList to aBodySet’s parts()
set pCount to pList’s |count|()
log pCount
–> 2

set aDtPath to POSIX path of (path to desktop)

–本来は再帰で処理すべき?
repeat with i in (pList as list)
  set aMimeType to (i’s mimeType()) as string
  
–> “text/plain”
  
  
set aCharset to (i’s charset()) as string
  
–> “UTF-8″
  
  
if aMimeType is in {“text/plain”, “text/html”} then
    set aStr to i’s decodedString()
    
  else if aMimeType = “application/zip” then
    set aData to i’s |data|()
    
set aFileName to (i’s filename()) as string
    
set fRes to writeDataToPath(aDtPath, aFileName, aData) of me
    
log fRes
    
  else
    –MimeTypeとcharsetがmissing valueのpart(たぶん、Attachment)
    
set ppList to i’s parts()
    
set ppCount to ppList’s |count|()
    
    
repeat with ii in (ppList as list)
      set bMimeType to (ii’s mimeType()) as string
      
set bCharset to (ii’s charset()) as string
      
      
if bMimeType is in {“text/plain”, “text/html”} then
        set aStr to ii’s decodedString()
        
      else if bMimeType = “application/zip” then
        set bData to ii’s |data|()
        
set bFileName to (ii’s filename()) as string
        
log bFileName
        
set fRes to writeDataToPath(aDtPath, bFileName, bData) of me
        
log fRes
      end if
    end repeat
    
  end if
  
end repeat

–NSDataをファイル書き込み。パス(aPath)はPOSIX pathで与える
on writeDataToPath(aPath, aName, aNSData)
  set newPath to aPath & aName
  
set aRes to aNSData’s writeToFile:newPath atomically:true
  
return aRes as boolean
end writeDataToPath

★Click Here to Open This Script 

2017/01/18 MailCore2でメールのeml形式ファイルを読み込む v2

オープンソースのフレームワーク「MailCore2」を使ってeml形式のメールファイルを読み込んでテキスト化するAppleScriptです。

実行のためにはMailCore.frameworkをビルドして、~/Library/Frameworksに入れておく必要があります。

本来、Mail.appでメールから本文テキストを取得するのは簡単で、小学生レベルの処理内容ですが本AppleScriptは、Mail.appから書き出されたemlファイル(Mail.appからドラッグ&ドロップで単体のメールを書き出したファイル)からの本文抽出を行います。

mail_eml.png

AppleScriptでなんともならない書類の筆頭であった「.eml」形式ファイルを読み込んでプレーンテキスト化できるようになったことには、大きな意義があるような気がします。

AppleScript名:MailCore2でメールのeml形式ファイルを読み込む v2
– Created 2017-01-18 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “MailCore” –https://github.com/MailCore/mailcore2
—http://piyocast.com/as/archives/4397

set aPath to POSIX path of (choose file of type {“com.apple.mail.email”})

set fileContents to current application’s NSData’s dataWithContentsOfFile:aPath options:(current application’s NSDataReadingMappedIfSafe) |error|:(missing value)
if fileContents = missing value then return

set aParser to current application’s MCOMessageParser’s messageParserWithData:fileContents

–改行を削除してメール本文をプレーンテキスト化
set aBody1 to (aParser’s plainTextBodyRendering()) as string
–>  ”技術書典 事務局です。 この度は技術書典2へのサークル参加申し込み、誠にありがとうございました。 ◎貴サークル「Piyomaru Software」は、 え-11 に配置されました。…

–改行を維持しつつメール本文をプレーンテキスト化
set aBody2 to (aParser’s plainTextBodyRenderingAndStripWhitespace:false) as string
(*
–>  ”技術書典 事務局です。
この度は技術書典2へのサークル参加申し込み、誠にありがとうございました。
◎貴サークル「Piyomaru Software」は、 え-11 に配置されました。
*)

★Click Here to Open This Script 

2017/01/03 Notesの指定エントリの本文をプレーンテキストとして取得

macOS標準搭載アプリ「メモ」(英語名:Notes)の指定エントリの本文をプレーンテキストとして取得するAppleScriptです。

メモの本文(body)がHTMLとして取得されるので、HTMLからスタイル付きテキスト(NSAttributedString)を経由してプレーンテキストに変換します。

AppleScript名:Notesの指定エントリの本文をプレーンテキストとして取得
– Created 2017-01-03 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

tell application “Notes”
  tell note 1 –とりあえず最新のエントリを指定
    set theBody to body
  end tell
end tell

set anNSString to current application’s NSString’s stringWithString:theBody
set theData to anNSString’s dataUsingEncoding:(current application’s NSUTF16StringEncoding)
set styledString to current application’s NSAttributedString’s alloc()’s initWithHTML:theData documentAttributes:(missing value)

set plainText to (styledString’s |string|()) as string

★Click Here to Open This Script 

2016/11/29 バンドル形式のAppleScript自身の「説明」を取得する v3

バンドル形式のAppleScript自身に書かれている「説明」の文章を取得するAppleScriptです。

path to meで自分自身のパスを取得したあと、ファイルタイプ(type identifier)を問い合わせるように変更しました。

AppleScript名:バンドル形式のAppleScript自身の「説明」(description.rtfd/TXT.rtf)を取得する v3
【コメント】 このScriptの説明文を書いておきますよー
– Created 2016-10-12 by Takaaki Naganoya
– Created 2016-11-28 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
–http://piyocast.com/as/archives/4268

set cRes to retMyComment() of me
–> "このScriptの説明文を書いておきますよー"

on retMyComment()
  set myPath to (path to me)
  
tell application "System Events"
    set myType to type identifier of myPath
  end tell
  
if myType is not equal to "com.apple.applescript.script-bundle" then return ""
  
  
set docPath to (myPath as string) & "Contents:Resources:description.rtfd:TXT.rtf"
  
set aRes to retTextFromRTF(docPath) of me
  
return aRes
end retMyComment

on retTextFromRTF(aFile)
  set aFilePath to current application’s NSString’s stringWithString:(POSIX path of aFile)
  
set aData to current application’s NSData’s dataWithContentsOfFile:aFilePath options:0 |error|:(missing value)
  
set theStyledText to current application’s NSMutableAttributedString’s alloc()’s initWithData:aData options:(missing value) documentAttributes:(missing value) |error|:(missing value)
  
  
if theStyledText is not equal to missing value then
    return (theStyledText’s |string|()) as string
  else
    return false –Not RTF file
  end if
end retTextFromRTF

★Click Here to Open This Script 

2016/11/05 RTF/RTFDを読み込んでテキスト抽出 v2

リッチテキストフォーマットのファイル(RTF/RTFD)を読み込んでテキストを抽出するAppleScriptの修正版です。

以前に掲載していたバージョンでも動いていたので気づかなかったのですが、オプションの指定に間違いがあり、missing valueと指定すべきところをnullと書いていました(汗)。最初の記述のままでもScript Editor上で動作していたものが、osascriptコマンド経由で実行するとかならずWarning Messageが出る。

 「osascript経由だとWarning Messageが出るのはなぜだー?」
 「それはお前のプログラムが間違っているからだー」

というやりとりをAppleのサポートと行って、Shaneに相談したところ「そこ、間違ってるぞ」という結論に(ーー;;

うん、確かに間違ってた。でも、osascriptコマンドには要らないWarning messageが不意に出力されてくるんで(choose fileコマンド実行時にQuickLookプラグインのWarningとか)、それを止められないのかというのがそもそも指摘したかった事項ではあるのですが、、、

あれ? Warnig Messageの発生源だった「MDQuickLook.qlgenerator」を調査してみたら、これが壊れていたので削除。これに起因するWarning Messageは出なくなりました。

AppleScript名:RTFを読み込んでテキスト抽出 v2
– Created 2016-09-29 by Takaaki Naganoya
– Modified 2016-11-05 by Shane Stanley
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4240
–http://piyocast.com/as/archives/4303

set aFile to choose file of type {“public.rtf”}
set aRes to retTextFromRTF(aFile) of me

on retTextFromRTF(aFile)
  set aFilePath to current application’s NSString’s stringWithString:(POSIX path of aFile)
  
set aData to current application’s NSData’s dataWithContentsOfFile:aFilePath options:0 |error|:(missing value)
  
set theStyledText to current application’s NSMutableAttributedString’s alloc()’s initWithData:aData options:(missing value) documentAttributes:(missing value) |error|:(missing value)
  
  
if theStyledText is not equal to missing value then
    return (theStyledText’s |string|()) as string
  else
    return false –Not RTF file
  end if
end retTextFromRTF

★Click Here to Open This Script 

AppleScript名:RTFDを読み込んでテキスト抽出 v2
– Created 2016-09-29 by Takaaki Naganoya
– Modified 2016-11-05 by Shane Stanley
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4240
–http://piyocast.com/as/archives/4303

set aFile to choose file of type {“com.apple.rtfd”}
set aRes to retTextFromRTFD(aFile) of me

on retTextFromRTFD(aFile)
  set aFilePath to current application’s NSString’s stringWithString:((POSIX path of aFile) & “/TXT.rtf”)
  
set aData to current application’s NSData’s dataWithContentsOfFile:aFilePath options:0 |error|:(missing value)
  
set theStyledText to current application’s NSMutableAttributedString’s alloc()’s initWithData:aData options:(missing value) documentAttributes:(missing value) |error|:(missing value)
  
if theStyledText is not equal to missing value then
    return (theStyledText’s |string|()) as string
  else
    return false –Not RTF file
  end if
end retTextFromRTFD

★Click Here to Open This Script 

2016/10/12 AppleScript自身の「説明」を取得する

実行中のAppleScript自身の「説明」欄に書かれたテキストを取得するAppleScriptです。

scriptcom1.jpg

AppleScript書類がスクリプトバンドル形式になっている場合にかぎられますが、実行中のAppleScript書類自体の「説明」欄に書かれているテキストを取得できます。

「説明」欄についてかる〜くご説明しておきますと、

「説明」欄にはそのAppleScript自体の説明やバージョンアップ履歴を書いておくことが多いようです。好きに使えます。そして、スタイル付きテキストを許容しますし、画像やムービーや音声をペーストしておくことも可能です。Applet書き出しを行い、初期画面を表示させるように設定しておくと、Applet起動時に表示されます。

AppleScriptをスクリプトバンドル形式(.scptd)で保存すると、「説明」欄の内容はバンドル中の、

  /Contents/Resources/description.rtfd

に記録されます。

scriptcom2.jpg

RTFではなくRTFDなので、さらにこのファイル自体がバンドルになっており、

  /Contents/Resources/description.rtfd/TXT.rtf

の中にリッチテキストが入っています。

scriptcom3.jpg

実行中のAppleScriptのパス(path to me)を取得し、そこからスクリプトバンドル中のrtf書類までのパスを組み立て、そこからテキストのみを抽出します。

scriptcmment4.jpg

最初、RTFのつもりでdescription.rtfdにアクセスして、テキストが取得できずに困ってしまいました。

なお、掲載しているAppleScriptのリストにも、文字色が黒なのでわかりにくいですが「説明」欄の内容が入っています(本Blog開設時の8年前から)。こちらは、スクリプトエディタに対してAppleScriptから問い合わせを行って取得しているもので、本Scriptとはアクセス方法が根本的に異なります。

AppleScript名:test script
【コメント】 このScriptの説明文を書いておきますよー
– Created 2016-10-12 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4268

set cRes to retMyComment() of me
–> “このScriptの説明文を書いておきますよー”

on retMyComment()
  set myPath to (path to me) as string
  
set docPath to myPath & “Contents:Resources:description.rtfd:TXT.rtf”
  
set aRes to retTextFromRTF(docPath) of me
  
return aRes
end retMyComment

on retTextFromRTF(aFile)
  set aFilePath to current application’s NSString’s stringWithString:(POSIX path of aFile)
  
set aData to current application’s NSData’s dataWithContentsOfFile:aFilePath options:0 |error|:(missing value)
  
set theStyledText to current application’s NSMutableAttributedString’s alloc()’s initWithData:aData options:(missing value) documentAttributes:(null) |error|:(missing value)
  
if theStyledText is not equal to missing value then
    return (theStyledText’s |string|()) as string
  else
    return false –Not RTF file
  end if
end retTextFromRTF

★Click Here to Open This Script 

2016/09/29 RTFを読み込んでテキスト抽出

指定のRTF(リッチテキスト)ファイルから、プレーンテキストを抽出するAppleScriptです。

→ 本プログラムには間違いがあるので、修正版を利用してください

不等号などの特殊記号について(≠、≥、≤)、スクリプトエディタ以外のサードパーティのAppleScriptエディタ(海外産)でエラーになる場合があるので(ASObjC Explorerとか)、大事をとって特殊記号を使わずに、「is not equal to」などと書くようにしています(あくまで個人的な見解です)。

このために、

tougou_resized.png

のように、Script Assistantで特殊記号類からこれらの英語表記を自動入力しやすいようにしています。

AppleScript名:RTFを読み込んでテキスト抽出
– Created 2016-09-29 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4240

set aFile to choose file of type {“public.rtf”}
set aRes to retTextFromRTF(aFile) of me

on retTextFromRTF(aFile)
  set aFilePath to current application’s NSString’s stringWithString:(POSIX path of aFile)
  
set aData to current application’s NSData’s dataWithContentsOfFile:aFilePath options:0 |error|:(missing value)
  
set theStyledText to current application’s NSMutableAttributedString’s alloc()’s initWithData:aData options:(missing value) documentAttributes:(null) |error|:(missing value)
  
  
if theStyledText is not equal to missing value then
    return (theStyledText’s |string|()) as string
  else
    return false –Not RTF file
  end if
end retTextFromRTF

★Click Here to Open This Script 

2016/09/13 並列ダウンロードのじっけん

複数のURLをリストで渡すと、Webからダウンロードを実行するAppleScriptです。

downloaded.jpg

実行すると、デスクトップにファイルを作成します。強制的に最前面(foreground)で実行する必要はありません。

本当に並列処理できているのか確証が持てなかったので、わりと大きなデータ(書籍のサンプルPDF)を指定してみました。たぶん、並列ダウンロードしていると思うんですが、、、、

オリジナルのObjective-Cのプログラムでは、毎回同じファイル名で保存するようになっていたので、URLからファイル名を求め(lastPathComponent())、その名前で保存するようにしてみました。

NSURLのlastPathComponent()は本当に便利です。DropboxのURLとかのパラメータつきURLから普通にPure AppleScriptでファイル名を抽出するのは面倒くさい処理ですが、Cocoaの機能を利用すると楽勝です。

ダウンロードしたデータ(NSData)を直接変数に代入できるので、「いったんファイルに書き込む処理をしたくない」ような場合に本ルーチンを書き換えてファイル保存しないようにすることで、目的を達成できます。

AppleScript名:並列ダウンロードのじっけん
– Created 2016-09-13 by Takaaki Naganoya
– 2016 Piyomaru Software

– オリジナル: HTTP からファイルをダウンロードして、ローカルに保存する方法(shintarou_horiのブログ)
–http://shintarou-hori.hatenablog.com/entry/2014/03/15/193604

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

set urlList to {“http://piyocast.com/as/wp-content/uploads/2016/08/book1_20_sampler.pdf”, “http://piyocast.com/as/wp-content/uploads/2016/08/book2_20_sampler.pdf”}

set fileCount to 1

repeat with i in urlList
  set j to contents of i
  
set aURL to (current application’s |NSURL|’s URLWithString:j)
  
set aQueue to current application’s NSOperationQueue’s new()
  
set aOperation to (current application’s NSInvocationOperation’s alloc()’s initWithTarget:me selector:“execDL:” object:aURL)
  (
aQueue’s addOperation:aOperation)
end repeat

on execDL:theURL
  set imageData to current application’s NSData’s alloc()’s initWithContentsOfURL:theURL
  
set aFileName to theURL’s |lastPathComponent|()
  
my performSelectorOnMainThread:“saveData:” withObject:{imageData, aFileName} waitUntilDone:false
end execDL:

on saveData:theDataArray
  copy theDataArray to {theData, saveFileName}
  
set documentsDirectory to current application’s NSString’s stringWithString:(POSIX path of (path to desktop folder))
  
set dataPath to documentsDirectory’s stringByAppendingPathComponent:saveFileName
  
set fileManager to current application’s NSFileManager’s defaultManager()
  
  
set success to (fileManager’s fileExistsAtPath:dataPath) as boolean
  
if success then
    set retData to current application’s NSData’s dataWithContentsOfFile:dataPath
  else
    theData’s writeToFile:dataPath atomically:true
  end if
end saveData:

★Click Here to Open This Script 

2016/03/07 与えられたNSDataに指定文字列をUTF-8のencodingでNSData化しつつ末尾に連結(結果は参照渡し)

与えられたNSDataに対して、文字列データをUTF-8で評価してNSDataにして追加するAppleScriptです。

NSDataへの追加について、appendData: usingEncoding: というメソッドがOS Xに存在していないようなので(iOSにあるのに)、サブルーチンで作っておきました。

巨大なデータ同士の追加になった場合への配慮として、サブルーチンを参照渡し(call by reference)で呼び出しています。

巨大なAppleScriptのプログラムを組む際には、一定以上に複雑にならないようになるべく値渡しのサブルーチンを用意し、単純な世界観で統一していますが、こういうケースは参照渡しだろうと。

AppleScript名:与えられたNSDataに指定文字列をUTF-8のencodingでNSData化しつつ末尾に連結(結果は参照渡し)
– Created 2016-03-07 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set postBody to current application’s NSMutableData’s |data|() –empty data

appendDataWithString(postBody, “123″)
postBody
–>  (NSData) <313233>

appendDataWithString(postBody, “456″)
postBody
–>  (NSData) <31323334 3536>

–与えられたNSDataに指定文字列をUTF-8のencodingでNSData化しつつ末尾に連結(結果は参照渡し)
on appendDataWithString(aData, aString)
  set aStr to current application’s NSString’s stringWithString:aString
  
set aBin to aStr’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
aData’s appendData:aBin
end appendDataWithString

★Click Here to Open This Script 

2016/02/06 指定URLのJSONをダウンロードしてRecordに変換 v2

指定URLのJSONデータを取得して、結果をrecordに変換するAppleScriptです。結果をrecordとして返すAPIのほかrecordのlistで返すものもあるようなので、対応させてみました。

AppleScript名:指定URLのJSONをダウンロードしてRecordに変換 v2
– Created 2016-02-06 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

on run
  –東京電力電力供給状況API JSON URL(指定された日時の電力使用状況を返す)
  
set a to downloadAndParseJSON("http://tepco-usage-api.appspot.com/2016/2/6/19.json") of me
  
–>  {forecast_peak_period:18, forecast_peak_usage:3670, capacity_updated:"2012-02-05 08:30:00", usage:3720, forecast_peak_updated:"2012-02-05 08:30:00", day:6, usage_updated:"2016-02-06 11:05:05", capacity:4484, saving:false, year:2016, month:2, capacity_peak_period:18, forecast:0, hour:19, entryfor:"2016-02-06 10:00:00"}
  
  
–東京電力電力供給状況API JSON URL(指定された日の毎時の電力使用状況を、配列として返す)
  
set b to downloadAndParseJSON("http://tepco-usage-api.appspot.com/2016/2/6.json") of me
  
–>  {{forecast_peak_period:18, forecast_peak_usage:3670, capacity_updated:"2012-02-05 08:30:00", usage:3081, forecast_peak_updated:"2012-02-05 08:30:00", day:6, usage_updated:"2016-02-05 16:05:06", capacity:4484, saving:false, year:2016, month:2, capacity_peak_period:18, forecast:0, hour:0, entryfor:"2016-02-05 15:00:00"}…….{forecast_peak_period:18, forecast_peak_usage:3670, capacity_updated:"2012-02-05 08:30:00", usage:3645, forecast_peak_updated:"2012-02-05 08:30:00", day:6, usage_updated:"2016-02-06 12:05:07", capacity:4484, saving:false, year:2016, month:2, capacity_peak_period:18, forecast:0, hour:20, entryfor:"2016-02-06 11:00:00"}}
  
  
  
–東京電力電力供給状況API JSON URL(指定された月の毎日毎時の電力使用状況を、配列として返す)
  
set c to downloadAndParseJSON("http://tepco-usage-api.appspot.com/2016/2.json") of me
  
–>  {{forecast_peak_period:18, forecast_peak_usage:4260, capacity_updated:"2012-01-31 23:30:00", usage:2901, forecast_peak_updated:"2012-01-31 23:30:00", day:1, usage_updated:"2016-01-31 16:05:04", capacity:4641, saving:false, year:2016, month:2, capacity_peak_period:18, forecast:0, hour:0, entryfor:"2016-01-31 15:00:00"}……{forecast_peak_period:18, forecast_peak_usage:3670, capacity_updated:"2012-02-05 08:30:00", usage:3645, forecast_peak_updated:"2012-02-05 08:30:00", day:6, usage_updated:"2016-02-06 12:05:07", capacity:4484, saving:false, year:2016, month:2, capacity_peak_period:18, forecast:0, hour:20, entryfor:"2016-02-06 11:00:00"}}
  
end run

on downloadAndParseJSON(aURL)
  set aRes to downloadDataFromWeb(aURL, 30) of me
  
if aRes = false then return false –download error
  
  
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)
  
  
—-recordとして評価して返す
  
try
    set aRec to aJsonDict as record
  end try
  
—-recordのlistとして評価して返す
  
try
    set aRec to aJsonDict as list
  on error
    return false
  end try
  
—–
  
return aRec
  
end downloadAndParseJSON

on downloadDataFromWeb(aURL, timeOutSec)
  set aURL to current application’s |NSURL|’s alloc()’s initWithString:aURL
  
set aReq to current application’s NSURLRequest’s alloc()’s initWithURL:aURL cachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeOutSec
  
set urlRes to (current application’s NSURLConnection’s sendSynchronousRequest:aReq returningResponse:(missing value) |error|:(missing value))
  
  
if urlRes = missing value then
    return false –Download Error
  else
    set aVal to (current application’s NSString’s alloc()’s initWithData:urlRes encoding:(current application’s NSUTF8StringEncoding))
    
return aVal
  end if
end downloadDataFromWeb

★Click Here to Open This Script 

2016/02/06 指定URLのJSONをダウンロードしてRecordに変換

指定URLのJSONデータを取得して、結果をrecordに変換するAppleScriptです。

Web上でいろいろサンプルを見繕っていたら、シンプルな例がなかなか存在しないため(わざと複雑に書いてあるのはなぜなんだろう?)、自分で簡単な例を書いてみました。

呼び出しているJSONのサービスは、@ssciさんが提供されている東京電力の最新の電力使用状況を返すものです。返ってくるデータの詳細については、「APIの使い方」をご参照ください

AppleScript名:指定URLのJSONをダウンロードしてRecordに変換
– Created 2015-12-16 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

on run
  –東京電力電力供給状況API JSON URL(最新の電力使用状況を返す)
  
set a to downloadAndParseJSON(“http://tepco-usage-api.appspot.com/latest.json”) of me
  
return a
  
–>  {forecast_peak_period:18, forecast_peak_usage:3670, capacity_updated:”2012-02-05 08:30:00″, usage:3720, forecast_peak_updated:”2012-02-05 08:30:00″, day:6, usage_updated:”2016-02-06 11:05:05″, capacity:4484, saving:false, year:2016, month:2, capacity_peak_period:18, forecast:0, hour:19, entryfor:”2016-02-06 10:00:00″}
end run

on downloadAndParseJSON(aURL)
  set aRes to downloadDataFromWeb(aURL, 30) of me
  
if aRes = false then return false –download error
  
  
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)
  
  
try
    set aRec to aJsonDict as record
  on error
    set aRec to false
  end try
  
  
return aRec
end downloadAndParseJSON

on downloadDataFromWeb(aURL, timeOutSec)
  set aURL to current application’s |NSURL|’s alloc()’s initWithString:aURL
  
set aReq to current application’s NSURLRequest’s alloc()’s initWithURL:aURL cachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeOutSec
  
set urlRes to (current application’s NSURLConnection’s sendSynchronousRequest:aReq returningResponse:(missing value) |error|:(missing value))
  
  
if urlRes = missing value then
    return false –Download Error
  else
    set aVal to (current application’s NSString’s alloc()’s initWithData:urlRes encoding:(current application’s NSUTF8StringEncoding))
    
return aVal
  end if
end downloadDataFromWeb

★Click Here to Open This Script 

2015/11/11 Safariでアクセス中のページが設定しているCookieの値を求める

Safariでアクセス中のページ(最前面のページ)で設定しているCookieの値を求めるAppleScriptです。

Safariの各種コントロールを日々行っていますが、Cookieの値を見て処理を分岐できると便利そうなケースがあったため、一応調べてみました。結局、そのページでは有用な情報はCookieから取得することはできなかったのですが、いつかどこかで有用な用途があるのでは? と考え掲載しておきます。

Cookieの値をURLデコードするように変更しました。

AppleScript名:Safariでアクセス中のページが設定しているCookie値を求める
– Created 2015-11-11by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
tell application "Safari"
  set dCount to count every document
  
if dCount = 0 then return
  
  
tell front document
    set aRes to do JavaScript "document.cookie"
  end tell
end tell
set bRes to retURLdecodedStrings(aRes) of me

set cookieList to parseByDelim(bRes, ";") of me
return cookieList

on parseByDelim(aData, aDelim)
  set aStr to current application’s NSString’s stringWithString:aData
  
set aDelimStr to current application’s NSString’s stringWithString:aDelim
  
set delimArray to aStr’s componentsSeparatedByString:aDelimStr
  
set aList to delimArray as list
  
return aList
end parseByDelim

on retURLencodedStrings(aText)
  set aStr to current application’s NSString’s stringWithString:aText
  
set encodedStr to aStr’s stringByAddingPercentEncodingWithAllowedCharacters:(current application’s NSCharacterSet’s alphanumericCharacterSet())
  
return encodedStr as text
end retURLencodedStrings

on retURLdecodedStrings(aURLencodedStr)
  set aStr to current application’s NSString’s stringWithString:aURLencodedStr
  
set aDecoded to aStr’s stringByRemovingPercentEncoding()
  
return aDecoded as text
end retURLdecodedStrings

★Click Here to Open This Script 

2015/10/03 文字エンコーディングを自動判別して日本語テキストファイル読み込み v1.2.1

Cocoaの機能と人間の知恵を総動員して未知の文字エンコーディングのテキストファイルに対して、文字エンコーディングを推測して「日本語の」テキストとして読み取るAppleScriptです。

未知といっておきながら、対象としているのは「UTF-8」「シフトJIS」「EUC」「ISO-2022JP」「UTF16(BOMなし)」、「UTF16(無表記)」「UTF-16BE」「UTF-16LE」です。メインフレームのEBCDICコードとかは想定していませんごめんなさいごめんなさい(誰に謝る?)。

「UTF-8」「シフトJIS」「EUC」「ISO-2022JP」については、判断方法が割と確立しているので安全に判定と読み取りが行えています。

問題は「それ以外の皆さん」(とくに「UTF16(無表記)」「UTF-16BE」「UTF-16LE」)です。

それぞれについて、文字コードチェックを総当たりで行い、問題がないものを採用するというのが順当なところですが、「問題があると判定されつつも問題がないパターン」「問題がないと判定されつつも問題があるパターン」のてんこもり。困ったところです。

解決策1:NSLinguisticTaggerに聞く

テキストを渡すと言語を判定してくれるNSLinguisticTaggerに聞いてみました。文字化けしていたら日本語として判定できないだろうという考えです。

サンプルのテキストはテクニカル系の記事(PC Watchの記事、SJISで11KB)と、ザ・近代文学ともいえる「坊ちゃん」(SJISで210KB)です。「坊ちゃん」のテキストを喰わせて「日本語」と判定できなかったらかなり問題ですが、そこは大丈夫でした。問題はアルファベットや数字が多いテクニカル系の記事で、NSLinguisticTaggerでは日本語として判定されませんでした(残念!)。

解決策2:行数と単語数をカウント

今度は、AppleScriptが持っている(最低限の)形態素解析能力を利用します(固有名詞を「連絡先」アプリや漢字変換辞書から引いてこないのがダメ)。OS X 10.6から、それ以前の「文字種類が変わったところで単語とみなす」というアホな単語切り分けをあらため、少しはマシな単語切り分けをするようになってきました(Mecabの機能を利用しているもよう。辞書固定だけど)

文字化けして謎の文字ばかり出てきた場合には、ほぼ1文字=1単語ぐらいに認識されるはずなので、経験的に言って「行数が1行の場合には文字化けしている」「単語数が多すぎる場合には文字化けしている」と言っても過言ではないでしょう。

これらを、多数決的に「UTF16(無表記)」「UTF-16BE」「UTF-16LE」「UTF16(BOMなし)」の処理結果を寄せ集めて判定するようにしてみました。

その結果がこちらです。

table1.png

v1.1の弱点を埋めようとして改良したv1.2において、逆に認識文字コードが減ってしまいました。ただし、認識順を調整するのではなしに「UTF16(無表記)」「UTF-16BE」「UTF-16LE」を総当たりでチェックして、その結果を評価すれば全部判定できるのでは? という気づきにつながりました(失敗は成功のマザー)。

手元に用意したサンプルの範囲では、ヒット率が100%。ぜひ多くの方々に使っていただいて、「ダメだった」パターンを集めてフィードバックしたいところです。こうして強化していけば、「文字コード判定のためだけにわざわざテキストエディタを操作する」とかいうアホな状況を回避できるものと思われます。

一応、El Capitan的にはEnumのBridgeが利くようになって、NSShiftJISStringEncodingといった定数をそのまま「current application’s NSShiftJISStringEncoding」などと書いています。ただ、数字の桁数がやたらと多いEnumは変数に入れたりした瞬間に指数表示になる(アホすぎる!)ので、変数にも代入できずに直接書いています。

AppleScript名:文字エンコーディングを自動判別してファイル読み込み v1.2.1
– Created 2014-12-28 by Takaaki Naganoya
– Modified 2014-12-29 by Shane Stanley
– Modified 2015-10-03 by Takaaki Naganoya
use AppleScript version “2.5″
use scripting additions
use framework “Foundation”

set aPath to POSIX path of (choose file)
set aRes to readJapanesTextFileWithGuessingEncoding(aPath) of me

–Read Japanese text with detecting its text encoding
on readJapanesTextFileWithGuessingEncoding(aPOSIXpath as string)
  
  
–ISO2022JP check
  
set aNSData to current application’s NSData’s dataWithContentsOfFile:aPOSIXpath
  
set aDataLength to aNSData’s |length|()
  
if aDataLength > 1024 then set aDataLength to 1024
  
  
–0×1B check
  
set anNSString to current application’s NSString’s stringWithString:(character id 27) – 0×1B
  
set theData to anNSString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set theRange to aNSData’s rangeOfData:theData options:0 range:(current application’s NSMakeRange(0, aDataLength))
  
  
–found 0×1B in aNSData
  
if |length| of theRange = 1 and location of theRange < aDataLength then
    set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:(current application’s NSISO2022JPStringEncoding)) –21
    
if aStr is not equal to missing value then return (aStr as text) – ISO2022JP
  end if
  
  
–EUC
  
set resValue to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:(current application’s NSJapaneseEUCStringEncoding))
  
if resValue is not equal to missing value then return (resValue as text)
  
–UTF-8
  
set resValue to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:(current application’s NSUTF8StringEncoding))
  
if resValue is not equal to missing value then return (resValue as text)
  
–SHift JIS
  
set resValue to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:(current application’s NSShiftJISStringEncoding))
  
if resValue is not equal to missing value then return (resValue as text)
  
  
  
–UTF-16BE/LE/無印Unicodeは多数決を取る
  
set resValue1 to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:(current application’s NSUTF16BigEndianStringEncoding)) as text
  
set sample1 to getTextSample(resValue1) of me
  
set lang1 to specifyLanguageOfText(sample1) of me
  
set para1 to length of (paragraphs of sample1)
  
set words1 to length of (words of sample1)
  
  
set resValue2 to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:(current application’s NSUTF16LittleEndianStringEncoding)) as text
  
set sample2 to getTextSample(resValue2) of me
  
set lang2 to specifyLanguageOfText(sample2) of me
  
set para2 to length of (paragraphs of sample2)
  
set words2 to length of (words of sample2)
  
  
set resValue3 to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:(current application’s NSUnicodeStringEncoding)) as text
  
set sample3 to getTextSample(resValue3) of me
  
set lang3 to specifyLanguageOfText(sample3) of me
  
set para3 to length of (paragraphs of sample3)
  
set words3 to length of (words of sample3)
  
  
–文字および文法的に見て「日本語」ならそれを返す
  
if lang1 = “ja” then return resValue1
  
if lang2 = “ja” then return resValue2
  
if lang3 = “ja” then return resValue2
  
  
  
–文字化けしたときには、日本語の「Word」として認識されづらく、Paragraphも少ない(1とか)なので条件で除外する
  
if para1 is not equal to 1 then
    if (words1 words2) or (words1 words3) then
      return resValue1
    end if
  end if
  
  
if para2 is not equal to 1 then
    if (words2 words1) or (words2 words3) then
      return resValue2
    end if
  end if
  
  
if para3 is not equal to 1 then
    if (words3 words1) or (words3 words2) then
      return resValue3
    end if
  end if
  
  
  
  
–おまけ(未確認)
  
set resValue to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:(current application’s NSWindowsCP1251StringEncoding))
  
if resValue is not equal to missing value then return resValue
  
  
set resValue to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:(current application’s NSWindowsCP1252StringEncoding))
  
if resValue is not equal to missing value then return resValue
  
  
set resValue to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:(current application’s NSWindowsCP1253StringEncoding))
  
if resValue is not equal to missing value then return resValue
  
  
set resValue to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:(current application’s NSWindowsCP1254StringEncoding))
  
if resValue is not equal to missing value then return resValue
  
  
set resValue to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:(current application’s NSWindowsCP1250StringEncoding))
  
if resValue is not equal to missing value then return resValue
  
  
return false
  
end readJapanesTextFileWithGuessingEncoding

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

on getTextSample(aText)
  set aLen to length of aText
  
if aLen < 1024 then
    set bLen to aLen
  else
    set bLen to 1024
  end if
  
return (text 1 thru bLen of aText)
end getTextSample

★Click Here to Open This Script 

2015/09/01 ASOCでエイリアエス書類のオリジナルファイル名およびフルパスを取得

Cocoaの機能を使って、エイリアス書類からオリジナルのファイル名およびフルパスを取得するAppleScriptです。

alias_doc.png

オリジナルはShaneがAppleScript Users MLに投稿したもの(オリジナルのファイル名を取得)ですが、フルパスが分からないと困るのでフルパスを求める機能を追加しました(フルパスを求めるKeyがなかなか見つからずに検索しまくりました)。

本Scriptのみどころは、まいどまいど「current application’s」と書いていた記述を、tellブロックで省略し、各行で「its ….」と書くことで省略記述を行う実験を(Shaneが)したところです。「なるほど、こう書けるのか〜」と感心しました。

Pure AppleScriptで書くと実にシンプルに書けるのですが、Finderに依存しすぎると困ることもあるので(Xcode上でAppleScriptObjCのプログラムを書く場合とか)、地道にこうしたルーチンを書き貯めていくのは重要なことです。

AppleScript名:ASOCでエイリアエスファイルのオリジナルファイル名およびフルパスを取得
– Created 2015-09-01 by Shane Stanley
– Modified 2015-09-01 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aFile to POSIX path of (choose file)
–>  ”/Users/me/Desktop/ma_VRD175A.pdf のエイリアス”–alias file

set aRes to my nameOfOriginalFileForAlias:aFile
–>  ”ma_VRD175A.pdf”–name

set bRes to my fullpathOfOriginalFileForAlias:aFile
–>  ”/Users/me/Desktop/ma_VRD175A.pdf”–full path

on nameOfOriginalFileForAlias:posixPath
  
  
tell current application’s class “NSURL”
    – make URL
    
set anNSURL to its fileURLWithPath:posixPath
    
–>  (NSURL) file:///Users/me/Desktop/ma_VRD175A.pdf%20%E…..
    
    
– load data from alias file
    
set theData to its bookmarkDataWithContentsOfURL:anNSURL |error|:(missing value)
    
–>  (NSData) <626f6f6b dc720900 00000410 <
>    

    
    
– get value as dictionary from data
    
set theResult to its resourceValuesForKeys:{current application’s NSURLLocalizedNameKey} fromBookmarkData:theData
    
–>  (NSDictionary) {​​​​​NSURLLocalizedNameKey:”ma_VRD175A.pdf”​​​}
    
  end tell
  
  
– extract name and coerce to text
  
return (theResult’s objectForKey:(current application’s NSURLLocalizedNameKey)) as text
  
end nameOfOriginalFileForAlias:

on fullpathOfOriginalFileForAlias:posixPath
  
  
tell current application’s class “NSURL”
    – make URL
    
set anNSURL to its fileURLWithPath:posixPath
    
–>  (NSURL) file:///Users/me/Desktop/ma_VRD175A.pdf%20%….
    
    
– load data from alias file
    
set theData to its bookmarkDataWithContentsOfURL:anNSURL |error|:(missing value)
    
–>  (NSData) <626f6f6b dc720900 00000410 <
>    

    
    
– get value as dictionary from data
    
set theResult to its resourceValuesForKeys:{current application’s NSURLPathKey} fromBookmarkData:theData
    
–>  (NSDictionary) {​​​​​_NSURLPathKey:”/Users/me/Desktop/ma_VRD175A.pdf”​​​}
    
  end tell
  
  
– extract name and coerce to text
  
return (theResult’s objectForKey:(current application’s NSURLPathKey)) as text
  
end fullpathOfOriginalFileForAlias:

★Click Here to Open This Script 

2015/08/15 ASOCでRTFの内容を読み取って指定文字を置換してファイルに保存する

Cocoaの機能を利用して、既存のRTF(リッチテキストフォーマット)の内容を読み取って、指定文字を置換するAppleScriptです。置換したあとのスタイル付きテキストをファイルに保存するところまでの処理を追加しました。

前バージョンを掲載したところ、(自分的には目的にかなう内容であったものの)Shaneからツッコミ。「ファイルに書かないと置換した内容の確認のしようがないじゃん(意訳)」という指摘により、保存処理を追加しました。

AppleScript名:ASOCでRTFの内容を読み取って指定文字を置換してファイルに保存する
– Created 2015-08-15 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

–Input
set aFile to POSIX path of (choose file of type {“public.rtf”})
set aFilePath to current application’s NSString’s stringWithString:aFile

–Output
set bFile to POSIX path of (choose file name with prompt “Choose replaced new RTF to save (with \”.rtf\” extension)”)
set bFilePath to current application’s NSString’s stringWithString:bFile

–Read RTF to NSData
set aData to current application’s NSData’s dataWithContentsOfFile:aFilePath options:0 |error|:(missing value)
set theStyledText to current application’s NSMutableAttributedString’s alloc()’s initWithData:aData options:(missing value) documentAttributes:(null) |error|:(missing value)
set astyledLength to theStyledText’s |string|()’s |length|()

–Replace Tag with String
theStyledText’s mutableString()’s replaceOccurrencesOfString:“<name>” withString:“長野谷” options:(current application’s NSCaseInsensitiveSearch) range:(current application’s NSMakeRange(0, astyledLength))

–Convert NSMutableStyledStrings to RTF
set bstyledLength to theStyledText’s |string|()’s |length|()
set bDict to current application’s NSDictionary’s dictionaryWithObject:“NSRTFTextDocumentType” forKey:(current application’s NSDocumentTypeDocumentAttribute)
–>  (NSDictionary) {​​​​​DocumentType:”NSRTFTextDocumentType”​​​}

set bRTF to theStyledText’s RTFFromRange:(current application’s NSMakeRange(0, bstyledLength)) documentAttributes:bDict

–Save to File
bRTF’s writeToFile:bFilePath atomically:true

★Click Here to Open This Script 

2015/08/14 ASOCでRTFの内容を読み取って指定文字を置換する

Cocoaの機能を利用して、既存のRTF(リッチテキストフォーマット)の内容を読み取って、指定文字を置換するAppleScriptです。

rtf1.png

こういう(↑)RTFを(OS X標準添付のTextEditなどで)作成したとして、本Scriptを実行すると、

rtf2.png

このようになります。RTFDの読み込みも実験しているのですが、こちらはなかなかうまくいきません。

AppleScript名:ASOCでRTFの内容を読み取って指定文字を置換する
– Created 2015-08-14 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aFile to POSIX path of (choose file of type {“public.rtf”})
set aFilePath to current application’s NSString’s stringWithString:aFile

set aData to current application’s NSData’s dataWithContentsOfFile:aFilePath options:0 |error|:(missing value)
set theStyledText to current application’s NSMutableAttributedString’s alloc()’s initWithData:aData options:(missing value) documentAttributes:(null) |error|:(missing value)
set astyledLength to theStyledText’s |string|()’s |length|()

theStyledText’s mutableString()’s replaceOccurrencesOfString:“<name>” withString:“長野谷” options:(current application’s NSCaseInsensitiveSearch) range:(current application’s NSMakeRange(0, astyledLength))

★Click Here to Open This Script 

2015/07/28 ASOCでbase64エンコード、デコード v2

とくに掲載するほどのものでもなかった「ASOCでbase64エンコード、デコード」でしたが、それ自体には価値がなかったものの、Shane Stanleyからいろいろ情報をもらえました(Thanks Shane!)。

「deprecated」扱いになっているmethodを使っている

「base64Encoding()」と「initWithBase64Encoding:」がdeprecated扱い

NSStringをas stringでcastするのはトラブルの元

OS X 10.11 El CapitanのAppleScript Release Noteに記載されているバグ修正情報にあるように、NSStringをas stringでcastすると、as textとかas unicode textでcast同様にすべての文字を保護するようになった・・・逆をいえば、as stringでcastするとデータが欠損する可能性があるバグが存在していましたということで・・・なかなか怖いバグです。

少なくとも、OS X 10.10.x上ではNSStringをas stringでcastすることは避けたほうがよさそうです。

AppleScript名:ASOCでbase64エンコード、デコード v2
– Created 2015-07-27 by Takaaki Naganoya
– Updated 2015-07-28 by Shane Stanley

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

–Encode
set aFile to POSIX path of (choose file with prompt “choose text file to test” of type {“public.html”, “public.text”})
set aEncStr to base64StringFromFileAtPath(aFile) of me

–Decode
set aRes to detaFromBase64String(aEncStr) of me

–Base64 Encode
on base64StringFromFileAtPath(aFilePath)
  set aDataFromFile to current application’s NSData’s dataWithContentsOfFile:aFilePath
  
set aBase64EncStr to aDataFromFile’s base64EncodedStringWithOptions:(current application’s NSDataBase64Encoding64CharacterLineLength)
  
return aBase64EncStr as text –stringではなくtext
end base64StringFromFileAtPath

–Base 64 Decode
on detaFromBase64String(aStr)
  set dataFrom64 to current application’s NSData’s alloc()’s initWithBase64EncodedString:aStr options:(current application’s NSDataBase64DecodingIgnoreUnknownCharacters)
  
set aStr to current application’s NSString’s alloc()’s initWithData:dataFrom64 encoding:(current application’s NSUTF8StringEncoding)
  
return aStr as text –stringではなくtext
end detaFromBase64String

★Click Here to Open This Script 

2015/07/27 ASOCでAppleScript設定ファイルの読み込み

Cocoaの機能を用いてScript Editor自身の書式設定ファイル(plist)を読み込むAppleScriptです。

とくに特殊な処理は必要なく、一般的なplistファイルに記録されたDictionaryをrecordに読み込んで、最終的にNSDataから各種データに復元しています。もうちょっとこなれた書き方もありそうですが、とりあえずデータを取り出すレベルまで書いてみた次第です。

Scriptが返している各種Cocoaのオブジェクトについては、Apple純正Script Editorでは表示されず、ASObjC Explorer 4で表示されるものを記載しています。

本当に色とフォント種別(+サイズほか関連情報)しか入っていないのにはびっくりです。

AppleScript名:ASOCでAppleScript設定ファイルの読み込み
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set AppleScriptFontFile to “~/Library/Preferences/com.apple.applescript.plist”
set pathString to current application’s NSString’s stringWithString:AppleScriptFontFile
set newPath to pathString’s stringByExpandingTildeInPath()
set aRec to retDictFromPlist(newPath) of me

set asAttr to AppleScriptSourceAttributes of aRec
set asAttrArray to {}

repeat with i in asAttr
  
  set aCol to NSColor of i
  
set aFont to NSFont of i
  
  set aColO to (current application’s NSUnarchiver’s unarchiveObjectWithData:aCol)
  
set aFonO to (current application’s NSUnarchiver’s unarchiveObjectWithData:aFont)
  
  set the end of asAttrArray to {aColO, aFonO}
  
end repeat

asAttrArray
–>  {​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.568627 0.156863 0.564706 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.372549 0.368627 0.368627 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.054902 0.243137 0.984314 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.470588 0.203922 0.796079 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.988235 0.164706 0.109804 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0 0 0 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.568627 0.321569 0.0666667 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0 0 0 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.152941 0.788235 0.788235 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.0588235 0.243137 0.984314 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.121569 0.713726 0.988235 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.505882 0.227451 0.85098 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.364706 0.211765 0.572549 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.0352941 0.215686 0.733333 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.0313726 0.215686 0.733333 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.0352941 0.215686 0.729412 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.337255 0.215686 0.741176 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}, ​​​​​{​​​​​​​(NSDeviceRGBColor) NSDeviceRGBColorSpace 0.203922 0.12549 0.388235 1, ​​​​​​​(NSFont) “Osaka 12.00 pt. P [] (0×600000249cc0) fobj=0×6000005e9800, spc=3.33″​​​​​}​​​}

–Read plist as record
on retDictFromPlist(aPath)
  set thePath to current application’s NSString’s stringWithString:aPath
  
set thePath to thePath’s stringByExpandingTildeInPath()
  
set theDict to current application’s NSDictionary’s dictionaryWithContentsOfFile:thePath
  
return theDict as record
end retDictFromPlist

★Click Here to Open This Script 

2015/01/26 テキストをhexdump(ASOC)v4

NSDataを使ってhexdumpを行うASOCのscriptの、若干の機能追加版です。

文字列の整形にCocoaの機能を使うように変更(データ量が増えた時にも安心できるように)しました。ただ、期待したよりは速くない感じです。

AppleScript名:テキストをhexdump(ASOC)v4
– Created 2015-01-26 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aStr to “あいうえお”

–Hexdump
set theNSString to current application’s NSString’s stringWithString:aStr
set theNSData to theNSString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
set theString to (theNSData’s |description|()’s uppercaseString())

–Remove “< " ">” characters in head and tail
set tLength to (theString’s |length|()) - 2
set aRange to current application’s NSMakeRange(1, tLength)
set theString2 to theString’s substringWithRange:aRange

–Replace Space Characters
set aString to current application’s NSString’s stringWithString:theString2
set bString to aString’s stringByReplacingOccurrencesOfString:” “ withString:“”

set aResList to splitString(bString, 2) as list
–> {​​​​​”E3″, ​​​​​”81″, ​​​​​”82″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”84″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”86″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”88″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”8A”​​​}

–Split NSString in specified aNum characters
on splitString(aText, aNum)
  
  
set aStr to current application’s NSString’s stringWithString:aText
  
if aStr’s |length|() aNum then return aText
  
  
set anArray to current application’s NSMutableArray’s new()
  
set mStr to current application’s NSMutableString’s stringWithString:aStr
  
  
set aRange to current application’s NSMakeRange(0, aNum)
  
  
repeat while (mStr’s |length|()) > 0
    if (mStr’s |length|()) < aNum then
      anArray’s addObject:(current application’s NSString’s stringWithString:mStr)
      
mStr’s deleteCharactersInRange:(current application’s NSMakeRange(0, mStr’s |length|()))
    else
      anArray’s addObject:(mStr’s substringWithRange:aRange)
      
mStr’s deleteCharactersInRange:aRange
    end if
  end repeat
  
  
return (current application’s NSArray’s arrayWithArray:anArray)
  
end splitString

★Click Here to Open This Script 

2015/01/25 テキストをhexdump(ASOC)v2

NSDataを使ってhexdumpを行うASOCのscriptの、若干の機能追加版です。

2文字ごとにペアにして出力するようにしました。

AppleScript名:テキストをhexdump(ASOC)v2
– Created 2015-01-25 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aStr to “あいうえお”
set theNSString to current application’s NSString’s stringWithString:aStr
set theNSData to theNSString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
set theText to text 2 thru -2 of (theNSData’s |description|()’s uppercaseString() as text)
set tList to words of theText
set ttext to tList as string

set aRes to my splitString:ttext everyChar:2
–> {​​​​​”E3″, ​​​​​”81″, ​​​​​”82″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”84″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”86″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”88″, ​​​​​”E3″, ​​​​​”81″, ​​​​​”8A”​​​}

–文字列を指定文字数で分割
on splitString:aText everyChar:aNum
  
  
set aStr to current application’s NSString’s stringWithString:aText
  
if aStr’s |length|() aNum then return aText
  
  
set anArray to current application’s NSMutableArray’s new()
  
set mStr to current application’s NSMutableString’s stringWithString:aStr
  
  
set aRange to current application’s NSMakeRange(0, aNum)
  
  
repeat while (mStr’s |length|()) > 0
    if (mStr’s |length|()) < aNum then
      anArray’s addObject:(current application’s NSString’s stringWithString:mStr)
      
mStr’s deleteCharactersInRange:(current application’s NSMakeRange(0, mStr’s |length|()))
    else
      anArray’s addObject:(mStr’s substringWithRange:aRange)
      
mStr’s deleteCharactersInRange:aRange
    end if
  end repeat
  
  
return (current application’s NSArray’s arrayWithArray:anArray) as list
  
end splitString:everyChar:

★Click Here to Open This Script 

2015/01/24 テキストをhexdump(ASOC)

何回もトライしてできていなかった、NSData経由でのデータのhexdump。Shane Stanleyから教えてもらいました。

ただ、しつこく何回も掲載している割には、現時点でこれといった用途はないのですが・・・この処理をクリアしておくときっとあとでいいことが。

AppleScript名:テキストをhexdump(ASOC)
– Created 2015-01-24 by Shane Stanley
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

set aStr to "あいうえお"
set theNSString to current application’s NSString’s stringWithString:aStr
set theNSData to theNSString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
set theText to text 2 thru -2 of (theNSData’s |description|()’s uppercaseString() as text)
–>  "E38182E3 8184E381 86E38188 E3818A"

★Click Here to Open This Script 

2014/12/29 文字エンコーディングを自動判別してファイル読み込み v1.1

Cocoaの機能を用いて、テキストファイルの文字エンコーディングを自動判別してテキストファイルを読み込むScriptの第3弾です。

最初に作ったv1と、Shane Stanleyから提案のあったYosemiteの新機能によるv2で、10KBぐらいの長めの日本語テキストをあらためて用意してテストしてみたところ、v2がUTF16BEとUTF16LEにまったく反応しないことが判明。

table2.png

ちょうどv1で仕方なく行っていたhexdumpの処理をShaneから提案のあった処理に置き換えてv1.1としたところ・・・v1系の方が反応できる文字エンコーディングが多いということに。

とりあえずこんな感じでしょうか?

AppleScript名:文字エンコーディングを自動判別してファイル読み込み v1.1
– Created 2014-12-28 by Takaaki Naganoya
– Changed 2014-12-29 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aPath to POSIX path of (choose file)
set aRes to readTextFile(aPath) of me

–Read Japanese text with detecting its text encoding
on readTextFile(aPath as string)
  
  
set aEnc to detectJapaneseEncodingAt(aPath) of me
  
if aEnc = false then return false
  
  
set aEncNum to aEnc as number
  
  
set encList to {“”, “”, current application’s NSJapaneseEUCStringEncoding, current application’s NSUTF8StringEncoding, “”, “”, “”, current application’s NSShiftJISStringEncoding, “”, current application’s NSUnicodeStringEncoding, current application’s NSWindowsCP1251StringEncoding, current application’s NSWindowsCP1252StringEncoding, current application’s NSWindowsCP1253StringEncoding, current application’s NSWindowsCP1254StringEncoding, current application’s NSWindowsCP1250StringEncoding, “”, “”, “”, “”, “”, current application’s NSISO2022JPStringEncoding}
  
  
if aEncNum > 21 then return false
  
  
set curEnc to contents of item aEncNum of encList
  
  
set {resValue, theError} to current application’s NSString’s stringWithContentsOfFile:aPath encoding:curEnc |error|:(reference)
  
return {aEncNum, resValue’s ASify() as string}
  
end readTextFile

–Auto detect text encoding
on detectJapaneseEncodingAt(aPOSIXpath as string)
  
  
–ISO2022JP check
  
set aNSData to current application’s NSData’s dataWithContentsOfFile:aPOSIXpath
  
set aDataLength to aNSData’s |length|()
  
if aDataLength > 1024 then set aDataLength to 1024
  
  
–0×1B check
  
set anNSString to current application’s NSString’s stringWithString:(character id 27) – 0×1B
  
set theData to anNSString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set theRange to aNSData’s rangeOfData:theData options:0 range:(current application’s NSMakeRange(0, aDataLength))
  
  
–found 0×1B in aNSData
  
if |length| of theRange = 1 and location of theRange < aDataLength then
    
    
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:21)
    
    
if aStr is not equal to missing value then
      return 21 – ISO2022JP
    end if
  end if
  
  
–Other Text Encodings
  
set encList to {“3″, “4″, “8″, “2415919360″, “2483028224″, “10″, “11″, “12″, “13″, “14″, “15″}
  
repeat with i in encList
    –UTF-16LE & BE parameters out of AppleScript number’s range. So, I held it in string data (But could not detect them….why?)
    
set j to contents of i
    
set aStr to (current application’s NSString’s stringWithString:j)
    
set aNum to aStr’s intValue()
    
    
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:aNum)
    
if aStr is not equal to missing value then
      return j as number
    end if
    
  end repeat
  
  
return false
  
end detectJapaneseEncodingAt

★Click Here to Open This Script 

2014/12/28 文字エンコーディングを自動判別してテキストファイル読み込み v2

Cocoaの機能を用いて、テキストファイルの文字エンコーディングを自動判別してテキストファイルを読み込むScriptの第2弾です。

Shane Stanleyから(many thanks!)「Yosemiteで追加された機能で、文字エンコーディングの自動検出ができるよ?」とScriptごとツッコミがあり、実際に試してみました。

・・・・・まあ、短い(汗)

EUC、SJIS、ISO2022-JP、UTF-8あたりはぜんぜん大丈夫です。UTF-16LE no BOMあたりがあやしいですが、Made In Japanの定番テキストエディタ「mi」v3がUTF-16LE no BOMは正しく読めなかった(逆に、海外産のTextWranglerで正しく読めたり)ぐらいなので、別によいのかもしれません。あと、UTF-16BEも怪しいです。

table1.png

AppleScriptのファイル書き出しでas unicode textを指定すると暗黙のうちにUTF-16BEが指定されるので、ASで書き出したテキスト「以外」なら自動判別させても大丈夫な感じがします。このあたり、どちらのルーチンを使うかについてはいろいろテストを行ってみるべきなんでしょう。

AppleScript名:Auto Text Encodings Detection
– Created 2014-12-28 by Shane Stanley
use AppleScript version “2.4″ – requires Yosemite
use scripting additions
use framework “Foundation”

set aPOSIXpath to POSIX path of (choose file)
set aRes to readTextDetectingTextEncodings(aPOSIXpath)
if aRes = false then return false

copy aRes to {theEncoding, theString, wasLossy}

–文字エンコーディングを自動判別してテキストファイル読み込み
on readTextDetectingTextEncodings(aPOSIXpath)
  
  
set anNSData to current application’s NSData’s dataWithContentsOfFile:aPOSIXpath
  
  
set theOptions to current application’s NSDictionary’s dictionaryWithObjects:{false, “ja”, {3, 4, 8, 10, 11, 12, 13, 14, 15, 21}} forKeys:{(current application’s NSStringEncodingDetectionAllowLossyKey), (current application’s NSStringEncodingDetectionLikelyLanguageKey), (current application’s NSStringEncodingDetectionSuggestedEncodingsKey)}
  
  
set {theEncoding, theString, wasLossy} to current application’s NSString’s stringEncodingForData:anNSData encodingOptions:theOptions convertedString:(reference) usedLossyConversion:(reference)
  
  
if theEncoding is 0 then return false
  
  
return {theEncoding, theString as string, wasLossy}
  
end readTextDetectingTextEncodings

★Click Here to Open This Script 

2014/12/28 文字エンコーディングを自動判別してテキストファイル読み込み v1

指定のテキストファイルの文字エンコーディングを自動判別してテキストを読み込むAppleScriptです。

シフトJIS、UTF-8、EUC、ISO2022JP、UTF16、UTF16LE、UTF16(BOMなし)などで動作を確認しています(が、まだ実験段階です。それほどイジめていないので)。

UTF16LE、UTF16(BOMなし)については、エンコーディングの自動判定ルーチンで「UTF-16」として判定され、正直「使えないかな?」という気もしていたのですが、UTF-16として読み込ませてみたら、とくに問題なく(文字化けもなく)読み込めてしまったので、結果オーライです(?)。

本Scriptは対象のテキストファイルを複数回走査しています。SSDのマシンではまったく気になりませんが、HDD経由だとやや遅いと感じるかもしれません。

 1回目:とりあえず読み込んでデータサイズを取得
 2回目:hexdumpコマンドで先頭から1024バイトをダンプ
 3回目:ISO2022JPで読み込みテスト
 4回目:他のエンコーディングで読み込みテスト×複数回

ライブラリに放り込んで使えば、割と便利に使えることでしょう。最終的には、テキストファイルのオープンを行うためだけにテキストエディタを併用しなくても済むようにしたいところです(そんなに作り込む気はないんですけれど)。

AppleScript名:文字エンコーディングを自動判別してファイル読み込み
– Created 2014-12-28 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aPath to POSIX path of (choose file)
set aRes to readTextFile(aPath) of me

–指定パスのテキストファイルを、文字エンコーディングを判定しつつ読み込む
on readTextFile(aPath as string)
  
  
set aEnc to detectJapaneseEncodingAt(aPath) of me
  
if aEnc = false then return false
  
  
set aEncNum to aEnc as number
  
  
set encList to {“”, “”, current application’s NSJapaneseEUCStringEncoding, current application’s NSUTF8StringEncoding, “”, “”, “”, current application’s NSShiftJISStringEncoding, “”, current application’s NSUnicodeStringEncoding, current application’s NSWindowsCP1251StringEncoding, current application’s NSWindowsCP1252StringEncoding, current application’s NSWindowsCP1253StringEncoding, current application’s NSWindowsCP1254StringEncoding, current application’s NSWindowsCP1250StringEncoding, “”, “”, “”, “”, “”, current application’s NSISO2022JPStringEncoding}
  
  
if aEncNum > 21 then return false
  
  
set curEnc to contents of item aEncNum of encList
  
  
set {resValue, theError} to current application’s NSString’s stringWithContentsOfFile:aPath encoding:curEnc |error|:(reference)
  
return resValue as string
  
end readTextFile

–指定パスのテキストファイルの文字エンコーディングを自動判定する
on detectJapaneseEncodingAt(aPOSIXpath as string)
  
  
–ISO2022JPのチェック
  
set aNSData to current application’s NSData’s dataWithContentsOfFile:aPOSIXpath
  
set aDataLength to aNSData’s |length|()
  
if aDataLength > 0 then
    
    
if aDataLength > 1024 then
      set aDataLength to 1024
    end if
    
    
set aRes to do shell script “/usr/bin/hexdump -n “ & (aDataLength as string) & ” “ & quoted form of aPOSIXpath
    
set bList to (current application’s SMSFord’s arrayFromCSV:aRes commaIs:” “) as list
    
set cList to (current application’s SMSFord’s arrayByFlattening:bList) as list
    
set dList to (current application’s SMSFord’s indexesOfItems:{“1b”} inArray:cList inverting:false)
    
set dLen to dList’s |count|()
    
    
if dLen > 1 then –1以上でもよかったが、なんとなく
      –NSISO2022JPStringEncodingチェック
      
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:21)
      
if aStr is not equal to missing value then
        return 21 – ISO2022JP
      end if
    end if
  else
    return false
  end if
  
  
–他のテキストエンコーディングのチェック
  
set encList to {“3″, “4″, “8″, “10″, “11″, “12″, “13″, “14″, “15″, “2415919360″, “2483028224″}
  
repeat with i in encList
    –数字でパラメータを持っておくと、AppleScriptの数値の上限を超えて指数表示になるため、あえて文字列で保持
    
set j to contents of i
    
set aStr to (current application’s NSString’s stringWithString:j)
    
set aNum to aStr’s intValue()
    
    
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:aNum)
    
if aStr is not equal to missing value then
      return j
    end if
    
  end repeat
  
  
return false
  
end detectJapaneseEncodingAt

★Click Here to Open This Script 

2014/12/28 テキストから文字エンコーディングを推測する v3

(日本語の)テキストファイルから、文字エンコーディングを推測するAppleScriptの改良版です。

v1では判定できなかったEUCとISO2022JPの区別を行ってみました。とりあえず理解するために、ISO2022JPのファイルを作成してバイナリエディタ「0xED」でバイナリダンプ。

bindump.png

データの傾向を把握して、さまざまなサイトのObjective-Cのサンプルコードを検討。0×1bのコードの存在確認を行う必要があるんだな、ということはわかりました。

本当はv1とv3の間に失敗作のv2があり、NSData経由でテキストファイルをバイナリとしてアクセスしてみたのですが・・・やり方がよくなかったのか、実行するとエディタ(ASObjCExplorer 4)がコケまくります。さすがに、Cocoaオブジェクトのログが出力できるASObjCExplorer 4でも、誤った記述を行うとエディタごと落ちます。

というわけで、格好つけずにhexdumpして文字として存在確認という「いつもの手口」で処理してみたのが、このv3です。

結果は良好で、ISO2022JP、EUC、UTF8、SJIS、UTF16については検出できるようになりました。

UTF16-BEとUTF-16LEについては、なぜか見てはいけない気がして試していません。

AppleScript名:テキストから文字エンコーディングを推測する v3
– Created 2014-12-28 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

(*

property NSJapaneseEUCStringEncoding : 3 –EUC
property NSUTF8StringEncoding : 4 –UTF8
property NSShiftJISStringEncoding : 8 –SJIS
property NSUnicodeStringEncoding : 10 –UTF16

property NSWindowsCP1251StringEncoding : 11
property NSWindowsCP1252StringEncoding : 12
property NSWindowsCP1253StringEncoding : 13
property NSWindowsCP1254StringEncoding : 14
property NSWindowsCP1250StringEncoding : 15
property NSISO2022JPStringEncoding : 21
property NSUTF16BigEndianStringEncoding : 2415919360 —UTF16BE
property NSUTF16LittleEndianStringEncoding : 2483028224 —UTF16LE

*)

set a to POSIX path of (choose file)
set aRes to detectJapaneseEncodingAt(a) of me
return aRes

on detectJapaneseEncodingAt(aPOSIXpath as string)
  
  
set encList to {“3″, “4″, “8″, “10″, “11″, “12″, “13″, “14″, “15″, “2415919360″, “2483028224″}
  
set hitList to {}
  
  
set aNSData to current application’s NSData’s dataWithContentsOfFile:aPOSIXpath
  
  
  
–ISO2022JPのチェック
  
set aDataLength to aNSData’s |length|()
  
if aDataLength > 0 then
    
    
if aDataLength < 64 then
      set aMax to aDataLength
    else
      set aMax to 64
    end if
    
    
–先頭から64バイトだけチェックすればいいだろー(NSData経由でバイナリサーチを試してダメだったのでshell経由で)
    
set aRes to do shell script “/usr/bin/hexdump -n “ & (aMax as string) & ” “ & quoted form of aPOSIXpath
    
set bList to (current application’s SMSFord’s arrayFromCSV:aRes commaIs:” “) as list
    
set cList to (current application’s SMSFord’s arrayByFlattening:bList) as list
    
set dList to (current application’s SMSFord’s indexesOfItems:{“1b”} inArray:cList inverting:false) as list
    
set dLen to length of dList
    
    
if dLen > 2 then –1以上でもよかったが、なんとなく
      –NSISO2022JPStringEncodingチェック
      
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:21)
      
if aStr is not equal to missing value then
        return {21, aStr}
      end if
    end if
  else
    return false
  end if
  
  
  
–他のテキストエンコーディングのチェック
  
repeat with i in encList
    –数字でパラメータを持っておくと、AppleScriptの数値の上限を超えて指数表示になるため、あえて文字列で保持
    
set j to contents of i
    
set aStr to (current application’s NSString’s stringWithString:j)
    
set aNum to aStr’s intValue()
    
    
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:aNum)
    
if aStr is not equal to missing value then
      return {j, aStr}
    end if
    
  end repeat
  
  
return hitList
  
end detectJapaneseEncodingAt

★Click Here to Open This Script 

2014/12/28 テキストから文字エンコーディングを推測する v1

(日本語の)テキストファイルから、文字エンコーディングを推測するAppleScriptです。

テキストエディタをAppleScriptで利用するケースはほとんどなくなっており、対象のテキストファイルの文字エンコーディングが不明の場合にのみ使ったりする程度でしょうか。

Cocoaの機能を用いてAppleScript単体で文字エンコーディングが判定できれば、テキストエディタを併用する必要性はほとんどなくなります。

・・・で、調べてみたところ・・・片っ端から総当たりでエンコーディングを指定してエラーになるかどうかテストするというのが主流のようで、

texts.png

かんたんな内容の、1つの文字エンコーディングで統一されている(同一ファイル中に複数のエンコーディングが存在しているケースは除外)ファイルを用意して、テストしてみました。

UTF8、UTF16、SJIS、EUCはヒットしましたが、ISO2022JPについてはEUCと判定されるので、別途判定ロジックが必要なようです。あと、UTF16BEとかUTF16LEについてはまだ調査していません。

とりあえず、テストを行ってみたという段階です。

AppleScript名:テキストから文字エンコーディングを推測する v1
– Created 2014-12-28 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "ASObjCExtras"

(*

property NSJapaneseEUCStringEncoding : 3 –EUC
property NSUTF8StringEncoding : 4 –UTF8
property NSShiftJISStringEncoding : 8 –SJIS
property NSUnicodeStringEncoding : 10 –UTF16

property NSWindowsCP1251StringEncoding : 11
property NSWindowsCP1252StringEncoding : 12
property NSWindowsCP1253StringEncoding : 13
property NSWindowsCP1254StringEncoding : 14
property NSWindowsCP1250StringEncoding : 15
property NSISO2022JPStringEncoding : 21
property NSUTF16BigEndianStringEncoding : 2415919360 —UTF16BE
property NSUTF16LittleEndianStringEncoding : 2483028224 —UTF16LE

*)

set a to POSIX path of (choose file)
set aData to current application’s NSData’s dataWithContentsOfFile:a
set aRes to detectJapaneseEncoding(aData) of me
return aRes

on detectJapaneseEncoding(aNSData)
  
  
set encList to {"3", "4", "8", "10", "11", "12", "13", "14", "15", "21", "2415919360", "2483028224"}
  
set hitList to {}
  
  
repeat with i in encList
    –数字でパラメータを持っておくと、AppleScriptの数値の上限を超えて指数表示になるため、あえて文字列で保持
    
set j to contents of i
    
set aStr to (current application’s NSString’s stringWithString:j)
    
set aNum to aStr’s intValue()
    
    
set aStr to (current application’s NSString’s alloc()’s initWithData:aNSData encoding:aNum)
    
if aStr is not equal to missing value then
      return {j, aStr}
    end if
    
  end repeat
  
  
return hitList
  
end detectJapaneseEncoding

★Click Here to Open This Script