Menu

Skip to content
AppleScriptの穴
  • Home
  • Products
  • Books
  • Docs
  • Events
  • Forum
  • About This Blog
  • License
  • 仕事依頼

AppleScriptの穴

Useful & Practical AppleScript archive. Click '★Click Here to Open This Script' Link to download each AppleScript

タグ: NSSortDescriptor

macOS 11、NSSortDescriptorでNSArrayを作ると100%クラッシュ

Posted on 6月 25, 2021 by Takaaki Naganoya

ものすごくありふれた「レコードをリストに入れたデータ」を高速でソートするために用いているサブルーチン「sortRecListByLabel」がmacOS 11でクラッシュするという情報を得て、調査してみたところたしかに100%クラッシュすることを確認しました。

具体的にどこでクラッシュするとかいえば、NSSortDescriptorでNSArrayを作る箇所、

set sortDesc to NSSortDescriptor's alloc()'s initWithKey:bLabelStr ascending:ascendF
set sortDescArray to NSArray's arrayWithObjects:sortDesc--This line cause crash!!!

ここです。

クラッシュを回避するためには、明示的にNSSortDescriptorでNSArrayを作成せず、Scripting Bridge経由で暗黙のlist→NSArray変換が行われるように書き換えること。こうすればクラッシュは発生しませんでした。

→ 訂正:もともとarrayWithObjectsでNSArrayを作る際には末尾にnull(missing value)が必要になるなど、昨今ではあまり使うことが推奨されない(廃止されつつある)やり方でした。NSArray arrayWithArray:などで作ればとくに問題ないことが発覚。よく見ると、ASOC初期の古いコードでそういうやり方をしていたものがあったようです。これまで「たまたま動いていた」というのが真相のようで、、、、(Thanks Shane!)

AppleScript名:asoc_レコードのリストをソート v3
— Created 2021-06-24 by Takaaki Naganoya
— 2021 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

script orig
  property aList : {}
end script

set (aList of orig) to {}

using terms from scripting additions
  repeat 100 times
    –乱数発生にrandom numberをmacOS 11+M1 Macで呼び出すと遅いので注意
    
set the end of aList of orig to {labelA:"a", labelB:(random number from 1 to 100)}
  end repeat
end using terms from

set bList to sortRecListByLabel((aList of orig), "labelB", false) of me –昇順ソート
return bList

–リストに入れたレコードを、指定の属性ラベルの値でソート
on sortRecListByLabel(aRecList as list, aLabelStr as string, ascendF as boolean)
  set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
set sortDesc to current application’s NSSortDescriptor’s alloc()’s initWithKey:aLabelStr ascending:ascendF
  
–set sortDescArray to current application’s NSArray’s arrayWithObjects:sortDesc–macOS 11でクラッシュ
  
set sortedArray to aArray’s sortedArrayUsingDescriptors:{sortDesc}
  
set bList to (sortedArray) as anything
  
return bList
end sortRecListByLabel

★Click Here to Open This Script 

Posted in list | Tagged 11.0savvy NSArray NSSortDescriptor | Leave a comment

AMChartsで円グラフをダイアログ上に表示 v3

Posted on 6月 27, 2020 by Takaaki Naganoya

アラートダイアログ上にWkWebViewを作成し、その上でAMChartsによる円グラフを表示するAppleScriptです。

ダイアログでテーマ選択したのちに、グラフ表示を行います。

—> Download ampiechartSample(AppleScript bundle with Script Library and HTMLs)

AppleScript名:AMChartsで円グラフをダイアログ上に表示 v3.scptd
—
–  Created by: Takaaki Naganoya
–  Created on: 2020/06/25
—
–  Copyright © 2020 Piyomaru Software, All Rights Reserved
—
use AppleScript version "2.4" — Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
use webD : script "webDialogLib"

set themeList to {"dark", "dataviz", "material", "kelly", "frozen", "moonrisekingdom", "spiritedaway"}

set aSel to (choose from list themeList with prompt "Select Theme")
if aSel = false then
  set aTheme to false
  
set aHTML to "index_notheme.html"
else
  set aTheme to contents of first item of aSel
  
if aTheme = "dark" then
    set aTheme to false
    
set aHTML to "index_dark.html"
  else
    set aHTML to "index.html"
  end if
end if

set aList to {{label:"ひよこ王国", value:403}, {label:"ぴよぴよ連邦", value:301}, {label:"ぴよランド", value:101}, {label:"ぴよー", value:65}}
set bList to sortRecListByLabel(aList, {"value"}, {false}) of me –降順ソート

–https://www.amcharts.com/demos/pie-chart/
set mePath to path to me
set resPath to (mePath as string) & "Contents:Resources:" & aHTML
set myStr to (read (resPath as alias) as «class utf8») as string

set jsonStr to array2DToJSONArray(bList) of me as string

if aTheme = false then
  set aString to current application’s NSString’s stringWithFormat_(myStr, jsonStr) as string
else
  set aString to current application’s NSString’s stringWithFormat_(myStr, aTheme, aTheme, jsonStr) as string
end if

set paramObj to {myMessage:"Simple Pie Chart", mySubMessage:"This is a AMCharts test", htmlStr:aString, jsDelimiters:{"<script>", "</script>"}, viewSize:{1000, 550}}

webD’s displayWebDialog(paramObj)

on array2DToJSONArray(aList)
  set anArray to current application’s NSMutableArray’s arrayWithArray:aList
  
set jsonData to current application’s NSJSONSerialization’s dataWithJSONObject:anArray options:(0 as integer) |error|:(missing value)
  
set resString to current application’s NSString’s alloc()’s initWithData:jsonData encoding:(current application’s NSUTF8StringEncoding)
  
return resString
end array2DToJSONArray

–リストに入れたレコードを、指定の属性ラベルの値でソート
on sortRecListByLabel(aRecList as list, aLabelStr as list, ascendF as list)
  set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
  
set aCount to length of aLabelStr
  
set sortDescArray to current application’s NSMutableArray’s new()
  
repeat with i from 1 to aCount
    set aLabel to (item i of aLabelStr)
    
set aKey to (item i of ascendF)
    
set sortDesc to (current application’s NSSortDescriptor’s alloc()’s initWithKey:aLabel ascending:aKey)
    (
sortDescArray’s addObject:sortDesc)
  end repeat
  
  
return (aArray’s sortedArrayUsingDescriptors:sortDescArray) as list
end sortRecListByLabel

★Click Here to Open This Script 

Posted in dialog list Sort | Tagged 10.13savvy 10.14savvy 10.15savvy 11.0savvy NSArray NSJSONSerialization NSMutableArray NSSortDescriptor NSString | Leave a comment

iTunes Libraryの再生回数をジャンルごとに集計、ジャンル名名寄せ付き

Posted on 12月 2, 2019 by Takaaki Naganoya

iTunesLibrary.framework経由でiTunes/Music.appのライブラリのジャンルごとの再生回数を集計するAppleScriptです。

7,000曲程度入っているライブラリで、ジャンル名の名寄せも含め、開発環境(MacBook Pro Retina 2012, Core i7 2.66GHz@macOS 10.14.6)で0.7秒程度で終了します。2,500曲程度入っているMac mini 2014 Core i5 2.6GHz@macOS 10.15.1で0.6秒程度です。

macOS 10.14まで(iTunes.app)と、macOS 10.15以降(Music.app)でも同様にiTunesLibrary.framework経由でライブラリへのアクセスが行えます。

AppleScript名:iTunes Libraryの再生回数をジャンルごとに集計、ジャンル名名寄せ付き
— Created 2019-11-10 by Takaaki Naganoya
— 2019 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "iTunesLibrary"
–https://developer.apple.com/reference/ituneslibrary/itlibmediaitem?language=objc

property NSArray : a reference to current application’s NSArray
property NSString : a reference to current application’s NSString
property NSScanner : a reference to current application’s NSScanner
property NSPredicate : a reference to current application’s NSPredicate
property NSDictionary : a reference to current application’s NSDictionary
property NSCountedSet : a reference to current application’s NSCountedSet
property NSMutableArray : a reference to current application’s NSMutableArray
property NSSortDescriptor : a reference to current application’s NSSortDescriptor
property NSMutableCharacterSet : a reference to current application’s NSMutableCharacterSet

–ジャンル名寄せリスト(2要素から構成される2D List)
set genreNayoseList to {{"World", "ワールド"}, {"Anime", "アニメ"}, {"Electronic", "エレクトロニック"}, {"R&B/ソウル", "R&B/ソウル"}, {"Kayokyoku", "歌謡曲"}, {"Electronic", "エレクトロニック"}, {"Vocal", "ヴォーカル"}, {"Classical", "クラシック"}, {"Dance", "ダンス"}, {"Soundtrack", "サウンドトラック"}, {"Rock", "ロック"}}

set library to current application’s ITLibrary’s libraryWithAPIVersion:"1.0" |error|:(missing value)
if library is equal to missing value then return

set aRes1 to (library’s applicationVersion()) as string –>  "12.10.1.37" @ macOS 10.15
set aRes2 to (library’s apiMinorVersion()) –> 1
set aRes3 to (library’s apiMajorVersion()) –> 1

set playLists to library’s allPlaylists()
set gArray to library’s allMediaItems()’s genre
set aRes to countItemsByItsAppearance(gArray) of me

set bRes to genreNayoseAndUnify(aRes, genreNayoseList) of me
–> {{genreName:"サウンドトラック", numberOfTimes:1965}, {numberOfTimes:1209, genreName:"Podcast"}, {genreName:"ロック", numberOfTimes:1128}, {genreName:"クラシック", numberOfTimes:705}, {numberOfTimes:517, genreName:"ポップ"}, {genreName:"アニメ", numberOfTimes:533}, {numberOfTimes:383, genreName:"J-Pop"}, {numberOfTimes:292, genreName:"Pop"}, {numberOfTimes:279, genreName:"社会/文化"}, {numberOfTimes:252, genreName:missing value}, {genreName:"ワールド", numberOfTimes:246}, {numberOfTimes:187, genreName:"ジャズ"}, {genreName:"エレクトロニック", numberOfTimes:168}, {numberOfTimes:125, genreName:"R&B"}, {numberOfTimes:104, genreName:"ニューエイジ"}, {numberOfTimes:81, genreName:"Unclassifiable"}, {genreName:"歌謡曲", numberOfTimes:60}, {numberOfTimes:57, genreName:"Children’s"}, {numberOfTimes:54, genreName:"オルタナティブ"}, {numberOfTimes:38, genreName:"Holiday"}, {numberOfTimes:32, genreName:"Data"}, {numberOfTimes:31, genreName:"イージーリスニング"}, {genreName:"ヴォーカル", numberOfTimes:31}, {numberOfTimes:19, genreName:"iTunes U"}, {numberOfTimes:17, genreName:"フォーク"}, {numberOfTimes:15, genreName:"ブルース"}, {numberOfTimes:15, genreName:"ディズニー"}, {numberOfTimes:15, genreName:"シンガーソングライター"}, {numberOfTimes:14, genreName:"Easy Listening"}, {numberOfTimes:14, genreName:"ラテン"}, {numberOfTimes:14, genreName:"Electronica/Dance"}, {numberOfTimes:14, genreName:"個人ジャーナル"}, {genreName:"ダンス", numberOfTimes:12}, {numberOfTimes:10, genreName:"アクション/アドベンチャー"}, {numberOfTimes:9, genreName:"J-POP"}, {numberOfTimes:9, genreName:"New Age"}, {numberOfTimes:7, genreName:"演歌"}, {numberOfTimes:6, genreName:"少年"}, {numberOfTimes:6, genreName:"青年"}, {numberOfTimes:6, genreName:"キッズ/ファミリー"}, {numberOfTimes:5, genreName:"Video"}, {numberOfTimes:5, genreName:"プログラミング"}, {numberOfTimes:4, genreName:"ホリデー"}, {numberOfTimes:4, genreName:"カントリー"}, {numberOfTimes:4, genreName:"科学/医学"}, {numberOfTimes:3, genreName:"ビジネス"}, {numberOfTimes:3, genreName:"コメディ"}, {numberOfTimes:3, genreName:"Game Music"}, {numberOfTimes:3, genreName:"Latin"}, {genreName:"R&B/ソウル", numberOfTimes:5}, {numberOfTimes:2, genreName:"#NIPPONSEI @ IRC.MIRCX.COM"}, {numberOfTimes:2, genreName:"Technology"}, {numberOfTimes:2, genreName:"ヒップホップ/ ラップ"}, {numberOfTimes:2, genreName:"ヒップホップ/ラップ"}, {numberOfTimes:2, genreName:"日本"}, {numberOfTimes:2, genreName:"ドラマ"}, {numberOfTimes:1, genreName:"社会科学"}, {numberOfTimes:1, genreName:"コンピュータ/テクノロジー"}, {numberOfTimes:1, genreName:"Tech ニュース"}, {numberOfTimes:1, genreName:"科学/自然"}, {numberOfTimes:1, genreName:"その他"}, {numberOfTimes:1, genreName:"児童書フィクション"}, {numberOfTimes:1, genreName:"レゲエ"}, {numberOfTimes:1, genreName:"Lifestyle & Home"}, {numberOfTimes:1, genreName:"ホリデーミュージック"}, {numberOfTimes:1, genreName:"マネジメント/リーダーシップ"}, {numberOfTimes:1, genreName:"インストゥルメンタル"}, {numberOfTimes:1, genreName:"SF/ファンタジー"}, {numberOfTimes:1, genreName:"146"}, {numberOfTimes:1, genreName:"健康/フィットネス"}, {numberOfTimes:1, genreName:"148"}, {numberOfTimes:1, genreName:"NHK FM(東京)"}, {numberOfTimes:1, genreName:"Seattle Pacific University – Latin"}, {numberOfTimes:1, genreName:"チルドレン・ミュージック"}, {numberOfTimes:1, genreName:"名作"}, {numberOfTimes:1, genreName:"Folk"}}

–ジャンルのリストを出現回数で集計
on countItemsByItsAppearance(aList)
  set aSet to NSCountedSet’s alloc()’s initWithArray:aList
  
set bArray to NSMutableArray’s array()
  
set theEnumerator to aSet’s objectEnumerator()
  
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
bArray’s addObject:(NSDictionary’s dictionaryWithObjects:{aValue, (aSet’s countForObject:aValue)} forKeys:{"genreName", "numberOfTimes"})
  end repeat
  
  
–出現回数(numberOfTimes)で降順ソート
  
set theDesc to NSSortDescriptor’s sortDescriptorWithKey:"numberOfTimes" ascending:false
  
bArray’s sortUsingDescriptors:{theDesc}
  
  
return bArray as list
end countItemsByItsAppearance

on genreNayoseAndUnify(aList as list, genreNayoseList as list)
  set gList to FlattenList(genreNayoseList) of me
  
  
set didProc to {}
  
  
set a2List to {}
  
  
repeat with i in aList
    set aGenre to genreName of i
    
    
if (aGenre is in gList) and (aGenre is not in didProc) then
      
      
repeat with ii in genreNayoseList
        set jj to contents of ii
        
        
if aGenre is in jj then
          copy jj to {g1, g2}
          
          
          
if chkAlphabet(g1) of me = true then
            set targG to g2
            
set targG2 to g1
          else
            set targG to g1
            
set targG2 to g2
          end if
          
          
          
set s1Res to searchByGenreName(aList, targG) of me
          
set s2Res to searchByGenreName(aList, targG2) of me
          
          
          
set s3Res to addMutipleLists({s1Res, s2Res}) of me
          
          
set tmpClass to class of s3Res
          
if tmpClass = list then
            set s3Res to contents of first item of s3Res
          end if
          
          
set outRec to {genreName:targG, numberOfTimes:s3Res}
          
set the end of didProc to g1
          
set the end of didProc to g2
          
          
exit repeat
        end if
        
      end repeat
      
      
set the end of a2List to outRec
    else
      if (aGenre is not in didProc) then
        set the end of a2List to contents of i
      end if
    end if
  end repeat
  
  
return a2List
end genreNayoseAndUnify

on searchByGenreName(aList as list, aGenreName as string)
  set predicatesStr to "genreName == ’" & aGenreName & "’"
  
set anArray to (NSArray’s arrayWithArray:aList)
  
set aPred to (NSPredicate’s predicateWithFormat:predicatesStr)
  
set bRes to (anArray’s filteredArrayUsingPredicate:aPred)
  
if (bRes as list) = {} then return {}
  
  
set bbRes to first item of bRes
  
return (numberOfTimes of bbRes) as list
end searchByGenreName

on addMutipleLists(s1List)
  script spdAdd
    property s1List : {}
    
property s3List : {}
  end script
  
  
copy s1List to (s1List of spdAdd) –init
  
  
set s1Len to length of first item of (s1List of spdAdd)
  
set (s3List of spdAdd) to makeZero1DList(s1Len, 0) of me
  
  
repeat with i in (s1List of spdAdd)
    set tmpLen to length of i
    
if tmpLen is not equal to s1Len then return false
    
    
repeat with ii from 1 to s1Len
      set tmp1 to contents of item ii of (s3List of spdAdd)
      
set tmp2 to contents of item ii of i
      
      
set item ii of (s3List of spdAdd) to (tmp1 + tmp2)
    end repeat
  end repeat
  
  
return (s3List of spdAdd)
end addMutipleLists

–指定要素を指定回数追加したリストを作成する
on makeZero1DList(itemMax, itemElem)
  set allData to {}
  
repeat itemMax times
    set the end of allData to itemElem
  end repeat
  
return allData
end makeZero1DList

–By Paul Berkowitz
–2009年1月27日 2:24:08:JST
–Re: Flattening Nested Lists
on FlattenList(aList)
  set oldDelims to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to {"????"}
  
set aString to aList as text
  
set aList to text items of aString
  
set AppleScript’s text item delimiters to oldDelims
  
return aList
end FlattenList

— アルファベットのみか調べて返す
on chkAlphabet(checkString)
  set aStr to NSString’s stringWithString:checkString
  
set allCharSet to NSMutableCharacterSet’s alloc()’s init()
  
allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of "a", 26))
  
allCharSet’s addCharactersInRange:(current application’s NSMakeRange(ASCII number of "A", 26))
  
set aBool to my chkCompareString:aStr baseString:allCharSet
  
return aBool as boolean
end chkAlphabet

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

★Click Here to Open This Script 

Posted in list | Tagged 10.12savvy 10.13savvy 10.14savvy 10.15savvy NSArray NSCountedSet NSDictionary NSMutableArray NSMutableCharacterSet NSPredicate NSScanner NSSortDescriptor NSString | Leave a comment

CotEditorの最前面のドキュメントの選択範囲を伏せ字に

Posted on 10月 22, 2019 by Takaaki Naganoya

CotEditorの最前面のドキュメントの選択範囲を、簡易形態素解析ルーチンeasyJparseを用いて、いい感じに伏せ字にするAppleScriptです。

–> Download makeSelectionToFuseji(Code-Signed AppleScript applet with libraries in its bundle, co-work with CotEditor)

easyJparseは日本語のコマンド解析用にでっちあげた作った超簡易形態素解析プログラムです。単語(形態素)ごとに分割しますが、品詞まではわかりません。コマンド解釈用ではあるものの、少し他の用途にも使えないかと思い、このような用途に使ってみました。

# 本Scriptは、CotEditor用のScript Pack v2.0に収録されています


▲CotEditorの選択範囲を伏せ字にする。形態素解析して単語化して、単語単位で伏せ字にするかの判断を実行

テキストエディタ上で伏せ字処理というのは、個人的によく使います。たいていは、オリジナルの文章に対して同様の分量の文章を作らなくてはならないようなケースで、文字数の感覚をつかむために使います。一種のダミーレイアウトのようなものです。

本スクリプトのような伏せ字処理については、ニーズがあるんだかないんだか不明なものですが、とりあえず掲載してみました。自分で使ってみたところ、たしかに面白いものの、実用性については未知数という印象です。

(minusList of parseSPD) に入れている語群は、どこかからか拾ってきたもののようではあるものの、すでに何か方向性を見失っているような気がしないではありません。

AppleScript名:選択範囲を伏せ字に(簡易形態素解析でそれっぽく).scptd
— Created 2018-09-26 by Takaaki Naganoya
— 2018 Piyomaru Software
use AppleScript version "2.5" — El Capitan (10.11) or later
use framework "Foundation"
use scripting additions
use jParser : script "easyJParse"

property NSArray : a reference to current application’s NSArray
property NSMutableSet : a reference to current application’s NSMutableSet
property NSSortDescriptor : a reference to current application’s NSSortDescriptor

property fuesejiChar : "□"

script parseSPD
  property pList : {}
  
property p2List : {}
  
property oneLine : {}
  
property outStr : ""
  
property minusList : {}
end script

–伏せ字化しない助詞などの単語リスト。名詞だけを残すように整備。単語(形態素)単位で照合する
set (minusList of parseSPD) to {"", " ", "ー", "あ", "で", "も", "に", "と", "の", "は", "へ", "さ", "が", "せ", "か", "た", "だ", "だっ", "ば", "つ", "な", "い", "き", "お", "ら", "る", "れ", "なっ", "それ", "これ", "あれ", "どれ", "この", "どの", "あの", "その", "まで", "こと", "もの", "いつ", "いく", "たち", "ただ", "たい", "そう", "いる", "よう", "れる", "ない", "なら", "なる", "なけれ", "から", "する", "たら", "たり", "だけ", "って", "られ", "的", "化", "いくら", "そんな", "どんな", "あんな", "者", "陰", "時", "事", "こんな", "つれ", "けど", "ああ", "ある", "あっ", "あり", "しかし", "きっと", "すっかり", "例えば", "たとえば", "さっぱり", "たとえ", "だろう", "かつ", "ところ", "まるで", "だが", "全て", "すべて", "なり", "いい", "つれ", "つけ", "ながら", "せいぜい", "そうそう", "さらに", "もっと", "まだ", "なく", "し", "を", "て", "いけ", "行く", "また", "まま", "まぁ", "『", "』", "、", "。", "。。", "……。", "【", "】", "「", "」", "(", ")", "最近", "今度", "中", "チカチカ", "グラグラ", "ふわふわ", "少し", "ついで", "より", "っぽい", "ぐらい", "何", "とき", "ため", "そっくり", "そして", "やがて", "じきに", "すぐ", "今", "次", "できる", "出来る", "いや", "そう", "おそらく", "いえ", "らしい", "とも", "ほぼ", "つい", "もう", "きっかけ", "ころ", "頃", "早々", "そこ", "どこ", "なんか", "じゃ", "くれ", "ください", "こそ", "あいつ", "だれ", "誰", "おぼしき", "らしき", "らしい", "しか", "でき", "よっ", "確か", "どう", "こう", "そう", "ああ", "くる", "ざま", "ごとく", "きれ", "はず", "さらに", "さらなる", "更なる", "など", "ごと", "とても", "たく", "いう", "とっ", "いっ", "えっ", "おっ", "ここ", "そこ", "どこ", "なかっ", "ごく", "やる", "ゆい", "ふと", "たび", "ほど", "もた", "よし", "ぜひ", "いら", "よい", "ま", "み", "む", "め", "も", "や", "けれど", "だけど", "したがっ", "すごく", "そもそも", "ほしい", "なれる", "すぎ", "もふもふ", "モフモフ", "さん", "おと", "とー", "えっと", "け", "っけ", "なん", "よ", "ね", "しっくり", "くれる", "くれた", "なぜ", "まあ", "まぁ", "ん", "なんて", "!」"}

set (pList of parseSPD) to {}
set (p2List of parseSPD) to {}
set (oneLine of parseSPD) to {}
set (outStr of parseSPD) to {}

tell application "CotEditor"
  tell front document
    –選択部分が存在しているかどうかチェック
    
set aCon to contents of selection
    
if aCon = "" then return
    
    
set (pList of parseSPD) to paragraphs of aCon
  end tell
end tell

–伏せ字にする対象単語を、助詞などを消し込むことでピックアップ
repeat with i in (pList of parseSPD)
  if length of i > 1 then
    –簡易形態素解析
    
set tempList to parseJ(i) of jParser
    
    
–簡易形態素解析したリストと助詞などのリストの差分を計算
    
set cList to clacListDiff(tempList, (minusList of parseSPD)) of me
    
    
set (oneLine of parseSPD) to {}
    
repeat with ii in tempList
      set aLen to length of ii
      
if ii is in cList then
        –伏せ字化する場合
        
set bCon to multipleChar(fuesejiChar, aLen) of me
        
      else
        –そのまま出力する場合
        
set bCon to contents of ii
      end if
      
set the end of (oneLine of parseSPD) to bCon
    end repeat
    
    
–1つの文章ぶんの単語を連結
    
set cStr to retDelimedText((oneLine of parseSPD), "") of me
  else
    set cStr to ""
  end if
  
  
set the end of (p2List of parseSPD) to cStr
  
end repeat

–すべての文章を連結して配列からテキストに
set (outStr of parseSPD) to retDelimedText((p2List of parseSPD), return) of me

tell application "CotEditor"
  tell front document
    set contents of selection to (outStr of parseSPD)
  end tell
end tell

–指定文字を指定回数繰り返して連結して出力
on multipleChar(aChar as string, aLen as integer)
  set aList to {}
  
repeat aLen times
    set the end of aList to aChar
  end repeat
  
  
return retDelimedText(aList, "") of me
end multipleChar

–1D Listを要素間に指定デリミタをはさんで文字列化
on retDelimedText(aList as list, aDelim as string)
  set aText to ""
  
set curDelim to AppleScript’s text item delimiters
  
set AppleScript’s text item delimiters to aDelim
  
set aText to aList as text
  
set AppleScript’s text item delimiters to curDelim
  
return aText
end retDelimedText

–2つの1D Listの差分を計算
on clacListDiff(aList as list, bList as list)
  set aSet to NSMutableSet’s setWithArray:aList
  
set bSet to NSMutableSet’s setWithArray:bList
  
  
aSet’s minusSet:bSet –補集合
  
set aRes to aSet’s allObjects() as list
  
  
return aRes
end clacListDiff

★Click Here to Open This Script 

Posted in list Natural Language Processing Text | Tagged 10.12savvy 10.13savvy 10.14savvy 10.15savvy CotEditor NSArray NSMutableSet NSSortDescriptor | 1 Comment

テキストから指定桁数の数値を抽出して度数分布集計

Posted on 6月 28, 2019 by Takaaki Naganoya

CotEditorで編集中の最前面の書類の本文中から指定桁の数値を抽出して登場回数で度数分布の集計を行うAppleScriptです。

CotEditorのアプリケーション内のスクリプトメニューに入れて実行できることを確認してあります(@macOS 10.14.5)。

たまたま、テキストで集計していた情報のうち、数値部分の度数分布を計算したいと考え、ありもののルーチンを組み合わせて作ったものです。

実行すると、集計対象の数値の桁数を聞いてくるため、適当なものを選択してださい。

CotEditorのScriptingの要注意点で、オブジェクト階層を素直に書いて、当該階層のオブジェクトの属性値を取りたいような場合に、そのまま属性を書いても取得できないケースが見られます。そのため、明示的にオブジェクト階層を指定するために「of it」を書いています。Xcodeが同じような挙動を行うため、注意が必要です。

本Scriptについていえば、正規表現でテキストから情報を抽出したりと、一応最低限のレベルはクリアしているものの、いまひとつ満足できません。

数字が連続して登場している箇所を抽出し、どれを集計対象にするのかといった分析を冒頭で行ってユーザーに問い合わせしたいところです。現段階では、「桁数を聞く」という頭の悪い実装になっていますが、そのうちどうにかしたいと考えるところです。

AppleScript名:テキストから指定桁数の数値を抽出して度数分布集計.scptd
— Created 2019-06-28 by Takaaki Naganoya
— 2019 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

property graphStr : "絆" –グラフに出力する文字

tell application "CotEditor"
  –最前面の書類からテキスト本文を取得
  
tell front document
    set aText to contents of it –本文
    
set lineTerm to (line ending) –改行コード
  end tell
  
  
–改行コードの選択(ドキュメントの状態から取得)
  
if lineTerm = LF then
    set aRet to ASCII character 10 –To avoid keyword conflict (string)
  else if lineTerm = CR then
    set aRet to ASCII character 13
  else if lineTerm = CRLF then
    set aRet to (ASCII character 13) & (ASCII character 10)
  else
    set aRet to ASCII character 10
  end if
end tell

–桁数選択
set digiList to {"2", "3", "4", "5"}
set digitRes to first item of (choose from list digiList with prompt "Choose the digit to extract as numbers")

–正規表現で数字部分のみ抽出
set theList to my findPattern:("[0-9]{" & digitRes & "}") inString:aText

–検出した数字部分の度数分布を計算
set theCountedSet to current application’s NSCountedSet’s alloc()’s initWithArray:theList
set newArray to current application’s NSMutableArray’s new()
set kList to uniquifyAndSort1DList(theList, false) of me –降順ソート

repeat with i in kList
  (newArray’s addObject:{theKey:i, theCount:(theCountedSet’s countForObject:i)})
end repeat

–出力用のテキストを作成
set outStr to ""
repeat with i in newArray as list
  set outStr to (outStr & (theKey of i) as string) & ":"
  
set aNum to (theCount of i) as integer
  
  
repeat aNum times
    set outStr to outStr & graphStr
  end repeat
  
  
set outStr to outStr & aRet
end repeat

–最前面のテキスト末尾に集計結果を出力
tell application "CotEditor"
  tell front document
    set contents of it to (aText & aRet & aRet & "集計結果:" & aRet & aRet & outStr & aRet)
  end tell
end tell

–正規表現でテキスト中から指定パターンに該当する箇所を抽出してリストで返す
on findPattern:thePattern inString:theString
  set theOptions to ((current application’s NSRegularExpressionDotMatchesLineSeparators) as integer) + ((current application’s NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to current application’s NSRegularExpression’s regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value)
  
set theFinds to theRegEx’s matchesInString:theString options:0 range:{location:0, |length|:length of theString}
  
set theFinds to theFinds as list — so we can loop through
  
set theResult to {} — we will add to this
  
set theNSString to current application’s NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set theRange to (item i of theFinds)’s range()
    
set end of theResult to (theNSString’s substringWithRange:theRange) as integer
  end repeat
  
return theResult
end findPattern:inString:

–1D Listをユニーク化してソート
on uniquifyAndSort1DList(theList as list, aBool as boolean)
  set aArray to current application’s NSArray’s arrayWithArray:theList
  
set bArray to aArray’s valueForKeyPath:"@distinctUnionOfObjects.self"
  
set aDdesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:"self" ascending:aBool selector:"compare:"
  
set cArray to bArray’s sortedArrayUsingDescriptors:{aDdesc}
  
  
set bList to cArray as list
  
return bList
end uniquifyAndSort1DList

★Click Here to Open This Script 

Posted in list Record regexp Sort Text | Tagged 10.12savvy 10.13savvy 10.14savvy CotEditor NSArray NSCountedSet NSMutableArray NSRegularExpression NSRegularExpressionDotMatchesLineSeparators NSSortDescriptor NSString | Leave a comment

Numbersで選択中の表のセルの範囲を背景色で頻度集計

Posted on 6月 6, 2019 by Takaaki Naganoya

Numbersでオープン中の書類の現在のシート上にある表の選択範囲のセルに対して、背景色(Background color)で登場頻度の集計を行って、どの色が何回登場しているかを色プレビューしつつ表示するAppleScriptです。

集計をRGB値で数値的に行うことは容易ですが、結局のところ色なんてRGB値で見せられてもわからず、実際に目で見える色で表示しないと判断できません。そのため、アラートダイアログを作成してその上にColorWellで表示しています。


▲Numbersの表でセルを選択


▲Numbersの表のセルに指定されている背景色の登場頻度で集計し、色プレビューと登場回数を表示(スクリプトエディタ@macOS 10.14.5上の表示、Dark Mode)


▲Numbersの表のセルに指定されている背景色の登場頻度で集計し、色プレビューと登場回数を表示(Script Debugger@macOS 10.14.5上の表示、Dark Mode)

上記サンプル表は本Blog上での説明用のために簡略化した表ですが、実際には数十行の入り組んだNumbersの表を相手にしており、かつ、手作業で色付きセルだけを数えるなんて考えたくもないレベルで頻繁に確認しなくてはならなかったために、本Scriptを作ってみました。

Numbersにかぎらず、iWorkアプリケーション(Keynote、Pages、Numbers)で共通の仕様ですが、背景色を塗らないセルから背景色を取得するとmissing valueが返ります。

本ScriptはScript Menuに入れて実行するとアラートダイアログが最前面に出てこないなど、Script書類(.scpt、.scptd)のままで実行すると問題が発生。Script Menuに入れる場合にはアプレット(.app)書き出しする必要があります。


▲スクリプトメニューにアプレット書き出しした実行ファイルを入れて呼び出し(macOS 10.14.5)。スクリプトメニューは最前面にあるGUIアプリケーションに応じて、それぞれのアプリケーション用のメニューを表示できるようになっている

AppleScript名:選択中の表のセルの範囲を背景色で頻度集計.scptd
—
–  Created by: Takaaki Naganoya
–  Created on: 2019/06/06
—
–  Copyright © 2019 Piyomaru Software, All Rights Reserved
—
use AppleScript version "2.4" — Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

property NSView : a reference to current application’s NSView
property NSAlert : a reference to current application’s NSAlert
property NSColor : a reference to current application’s NSColor
property NSIndexSet : a reference to current application’s NSIndexSet
property NSTextField : a reference to current application’s NSTextField
property NSColorWell : a reference to current application’s NSColorWell
property NSScrollView : a reference to current application’s NSScrollView
property NSMutableArray : a reference to current application’s NSMutableArray
property NSRunningApplication : a reference to current application’s NSRunningApplication

property theResult : 0
property returnCode : 0
property theDataSource : {}

tell application "Numbers"
  tell front document
    tell active sheet
      try
        set theTable to first table whose class of selection range is range
      on error
        return "No Selection" –何も選択されてなかった場合
      end try
      
      
tell theTable
        set bgColList to background color of every cell of selection range
        
–> {{18572, 45937, 65452}, {18572, 45937, 65452}, {3001, 24801, 44056}, {18572, 45937, 65452}, {18572, 45937, 65452}, {64899, 33134, 31509}, {64899, 33134, 31509}, {18572, 45937, 65452}, {18572, 45937, 65452}, {3001, 24801, 44056}, {18572, 45937, 65452}, {18572, 45937, 65452}, {26149, 65534, 58650}, {26149, 65534, 58650}, {26149, 65534, 58650}, {26149, 65534, 58650}, {18572, 45937, 65452}, {18572, 45937, 65452}}
      end tell
    end tell
  end tell
end tell

set bgColList to countItemsByItsAppearance(bgColList) of me
–> {{theName:{65535, 65533, 65534}, numberOfTimes:73}, {theName:{65535, 65535, 21706}, numberOfTimes:73}, {theName:missing value, numberOfTimes:27}}

set paramObj to {mainDat:bgColList, myTitle:"色別集計", mySubTitle:"Numbers上で選択したセルの背景色の頻度集計"}
–my browseColors:paramObj–for debug

my performSelectorOnMainThread:"browseColors:" withObject:(paramObj) waitUntilDone:true

on browseColors:paramObj
  set aParamList to (mainDat of (paramObj as record)) as list
  
set winTitle to (myTitle of (paramObj as record)) as string
  
set subTitle to (mySubTitle of (paramObj as record)) as string
  
  
set aLen to length of aParamList
  
  
set aHeight to 100
  
set aWidth to 300
  
  
–NSViewをつくる
  
set aNSV to NSView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight * 1.5))
  
aNSV’s setNeedsDisplay:true
  
  
— NSScroll Viewをつくる
  
set aScroll to NSScrollView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight))
  
aScroll’s setDocumentView:aNSV
  
  
aNSV’s enclosingScrollView()’s setHasHorizontalScroller:true
  
aNSV’s enclosingScrollView()’s setHasVerticalScroller:true
  
  
–NSColorWell+ NSTextFieldをつくる
  
set aStep to 1
  
repeat with i in aParamList
    set aColorWell to (NSColorWell’s alloc()’s initWithFrame:(current application’s NSMakeRect(10, ((aLen – aStep + 1) * 30), 60, 20)))
    
    
set myName to (theName of i)
    
set myTimes to (numberOfTimes of i)
    
    
if myName is not equal to missing value then
      copy (myName as list) to {rNum, gNum, bNum}
      
set myTimes to numberOfTimes of i
      
      
set myColor to makeNSColorFromRGBAval(rNum, gNum, bNum, 65535, 65535) of me
      (
aColorWell’s setColor:myColor)
      (
aColorWell’s setBordered:false)
      (
aNSV’s addSubview:aColorWell)
      
      
set aTF to makeNSTextField(80, ((aLen – aStep + 1) * 30), 100, 20, false, myTimes as string, true, true) of me
      (
aNSV’s addSubview:aTF)
      
      
set aStep to aStep + 1
    end if
  end repeat
  
  
–NSScrollViewを強制的にトップにスクロール
  
set aPT to current application’s NSMakePoint(0.0, aHeight)
  
aScroll’s documentView()’s scrollPoint:aPT
  
  
— set up alert  
  
set theAlert to NSAlert’s alloc()’s init()
  
tell theAlert
    its setMessageText:winTitle
    
its setInformativeText:subTitle
    
its addButtonWithTitle:"OK"
    
its addButtonWithTitle:"Cancel"
    
its setAccessoryView:aScroll
  end tell
  
  
— show alert in modal loop
  
NSRunningApplication’s currentApplication()’s activateWithOptions:0
  
my performSelectorOnMainThread:"doModal:" withObject:(theAlert) waitUntilDone:true
  
if (my returnCode as number) = 1001 then error number -128
  
end browseColors:

on doModal:aParam
  set (my returnCode) to aParam’s runModal()
end doModal:

on makeNSColorFromRGBAval(redValue as integer, greenValue as integer, blueValue as integer, alphaValue as integer, aMaxVal as integer)
  set aRedCocoa to (redValue / aMaxVal) as real
  
set aGreenCocoa to (greenValue / aMaxVal) as real
  
set aBlueCocoa to (blueValue / aMaxVal) as real
  
set aAlphaCocoa to (alphaValue / aMaxVal) as real
  
set aColor to NSColor’s colorWithCalibratedRed:aRedCocoa green:aGreenCocoa blue:aBlueCocoa alpha:aAlphaCocoa
  
return aColor
end makeNSColorFromRGBAval

–1D Listを要素別に出現回数で集計
on countItemsByItsAppearance(aList as list)
  set aSet to current application’s NSCountedSet’s alloc()’s initWithArray:aList
  
set bArray to current application’s NSMutableArray’s array()
  
set theEnumerator to aSet’s objectEnumerator()
  
  
repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
bArray’s addObject:(current application’s NSDictionary’s dictionaryWithObjects:{aValue, (aSet’s countForObject:aValue)} forKeys:{"theName", "numberOfTimes"})
  end repeat
  
  
–出現回数(numberOfTimes)で降順ソート
  
set theDesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:"numberOfTimes" ascending:false
  
bArray’s sortUsingDescriptors:{theDesc}
  
  
return bArray as list
end countItemsByItsAppearance

on makeNSTextField(xPos as integer, yPos as integer, myWidth as integer, myHeight as integer, editableF as boolean, setVal as string, backgroundF as boolean, borderedF as boolean)
  set aNSString to NSTextField’s alloc()’s initWithFrame:(current application’s NSMakeRect(xPos, yPos, myWidth, myHeight))
  
aNSString’s setEditable:(editableF)
  
aNSString’s setStringValue:(setVal)
  
aNSString’s setDrawsBackground:(backgroundF)
  
aNSString’s setBordered:(borderedF)
  
return aNSString
end makeNSTextField

★Click Here to Open This Script 

Posted in Color dialog list Record | Tagged 10.12savvy 10.13savvy 10.14savvy NSAlert NSColor NSColorWell NSCountedSet NSDictionary NSIndexSet NSMutableArray NSRunningApplication NSScrollView NSSortDescriptor NSTextField NSView Numbers | Leave a comment

レコードのリストを複数Keyでソート

Posted on 4月 19, 2019 by Takaaki Naganoya

リストに入れたレコードを複数キー、キーごとのソート方向(ascending/descending)指定でソートするAppleScriptです。

ソート対象のラベルはサンプルのように階層構造ごと指定できます。キーとして列挙するラベルは、最初のものが主キーで、2番目以降はサブキーです。

AppleScript名:レコードのリストを複数Keyでソート.scptd
—
–  Created by: Takaaki Naganoya
–  Created on: 2019/04/19
—
–  Copyright © 2019 Piyomaru Software, All Rights Reserved
—
use AppleScript version "2.5"
use scripting additions
use framework "Foundation"

set aRecList to {{aName:"piyoko", aVal:10, bVal:{x:3, y:1}}, {aName:"piyomaru", aVal:20, bVal:{x:5, y:2}}, {aName:"piyoo", aVal:20, bVal:{x:4, y:5}}, {aName:"piyozo", aVal:10, bVal:{x:10, y:9}}}

set bList to sortRecListByLabel(aRecList, {"aVal", "bVal.x"}, {true, false}) of me
–> {{aName:"piyozo", aVal:10, bVal:{x:10, y:9}}, {aName:"piyoko", aVal:10, bVal:{x:3, y:1}}, {aName:"piyomaru", aVal:20, bVal:{x:5, y:2}}, {aName:"piyoo", aVal:20, bVal:{x:4, y:5}}}

–リストに入れたレコードを、指定の属性ラベルの値でソート
on sortRecListByLabel(aRecList as list, aLabelStr as list, ascendF as list)
  set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
  
set aCount to length of aLabelStr
  
set sortDescArray to current application’s NSMutableArray’s new()
  
repeat with i from 1 to aCount
    set aLabel to (item i of aLabelStr)
    
set aKey to (item i of ascendF)
    
set sortDesc to (current application’s NSSortDescriptor’s alloc()’s initWithKey:aLabel ascending:aKey)
    (
sortDescArray’s addObject:sortDesc)
  end repeat
  
  
return (aArray’s sortedArrayUsingDescriptors:sortDescArray) as list
end sortRecListByLabel

★Click Here to Open This Script 

Posted in list Record Sort | Tagged 10.11savvy 10.12savvy 10.13savvy 10.14savvy NSArray NSMutableArray NSSortDescriptor | Leave a comment

機械学習で学習したMSの画像を連邦・ジオン軍判定してフォルダ分け v2

Posted on 11月 20, 2018 by Takaaki Naganoya

Create MLでガンダムのMS画像(連邦、ジオン区分け)を学習したCore ML Modelをもとに、画像をどちらの軍所属機体かを判定してフォルダ分けするAppleScriptです。

機械学習(深層学習)データをもとに判定を行って対話型処理ではなくバッチ処理的なフローでデータを「なんとなく」推論して区分けを行うサンプルです。非力なIOT機器が機械学習データをもとに推論処理できるぐらいなので、機械学習データを使ってAppleScriptで推論処理できないはずがありません。推論処理時間もさほどかからず、機械学習データを一括処理的なワークフローの中で利用することが可能です。

例によって「戦場の絆」の公式ページから落としてきたMSの画像を連邦軍とジオン軍、および両軍の鹵獲機体の4フォルダに分けてCreate MLで学習させてみました。

実行するとMS画像が入っているフォルダ(各自ご用意ください)と、連邦軍機体(efsf)およびジオン軍機体(zeon)を仕分けるフォルダを聞かれます。あとはプログラム側がなんとなく推論して仕分けを行います。

前回掲載のモデルもそうですが、きちんと学習後にテストデータで評価を行っています。だいたい8割ぐらいのヒット率ではあるものの、特定の画像については間違えることがあらかじめわかっています。

実行にあたっては、macOS 10.14上でCoreML Modelを組み込んだフレームワーク「msDetector.framework」をインストールして、本ScriptをScript Debugger上で動作させてください。

–> Download msDetector.framework (To ~/Library/Frameworks/)

–> Download msDetector (Code Signed executable AppleScript applet with Framework and libraries in its bundle)

Core MLで判定時に詳細なデータを出力させ、その可能性が0.8を上回るものだけを処理するようにしてみました。ただ、全力で間違った判定を行うケースもあるため、単なる「気休め」です。

テストデータでは、連邦軍側が72機体中間違いが9個(正解87.5%)、ジオン軍側が57機体中間違いが3個(正解95%)。鹵獲機体のデータを排除すると、もう少しいい値が出るかもしれません。ただ、学習させたデータとほぼ同じデータ(数が少ない)で再度判定を行っているだけなので、このぐらいの確度が出ないと逆に困ります。

また、確度が高くないものは排除するように処理したので、確度が低い機体の画像バリエーションを増やして学習画像数を足すと正解の確率が上がるのではないか、などと「なんとなく」考えています。

Create MLでは、もっときちんとした表データやタグ付き自然言語テキストデータ(日本語だと形態素への区分けはあらかじめやっておく必要アリ?)の学習が行えるようなので(自然言語テキストの学習例は英語のものしか見かけないけれども)いろいろやっておきたいところです。

今回は「アニメとかゲームに出てくるMSの画像」の判定という、実用性をまったく考えないで行なってみたものですが、自分の身の回りで有用性の高そうな処理といえば……アイコンやプレゼン用資料の作成画像を学習させておおまかなジャンル分けをしておき(メール系のアイコンとか)、未知のアイコン素材をダウンロードしてきたら機械学習データをもとに自動でフォルダ分けするとかでしょうか。

さすがにアイコン画像だとデフォルメされすぎていて、MicrosoftのAzure Computer Vision APIとかでも画像認識してくれなさそうなので。

AppleScript名:機械学習で学習したMSの画像の連邦軍、ジオン軍判定してフォルダ分け v2.scpt
—
–  Created by: Takaaki Naganoya
–  Created on: 2018/11/20
—
–  Copyright © 2018 Piyomaru Software, All Rights Reserved
—
use AppleScript version "2.7" — Mojave (10.14) or later & Script Debugger
use framework "Foundation"
use framework "AppKit"
use framework "msDetector"
use scripting additions

property |NSURL| : a reference to current application’s |NSURL|
property NSArray : a reference to current application’s NSArray
property NSString : a reference to current application’s NSString
property NSPredicate : a reference to current application’s NSPredicate
property NSFileManager : a reference to current application’s NSFileManager
property NSMutableArray : a reference to current application’s NSMutableArray
property NSSortDescriptor : a reference to current application’s NSSortDescriptor
property NSURLTypeIdentifierKey : a reference to current application’s NSURLTypeIdentifierKey

set aFol to POSIX path of (choose folder with prompt "Choose MS Image folder to go!")
set efsfFol to POSIX path of (choose folder with prompt "EFSF folder")
set zeonFol to POSIX path of (choose folder with prompt "Zeon folder")

–指定フォルダ以下のファイルをすべて取得(再帰ですべて取得)
set aSel to retFullpathsWithinAFolderWithRecursive(aFol) of me

–取得したファイルリストを指定UTIでフィルタリング
set filRes2 to filterPOSIXpathListByUTI(aSel, "public.image") of me

–機械学習モデルを用いて区分けを行い、任意の画像の連邦軍(efsf)、ジオン軍(zeon)へのフォルダ分け(コピー)
set msClassifier to current application’s msClassify’s alloc()’s init()

repeat with i in filRes2
  set aFile to contents of i
  
set aImage to (current application’s NSImage’s alloc()’s initWithContentsOfFile:aFile)
  
  
set resDict to (msClassifier’s ImageClassifierWithImageAndRetDict:aImage) –returns NSDictionary
  
  
–Dictinaryのvalueをソートして、最も値の大きいvalueのkeyを求める
  
set sortedArray to (current application’s NSMutableArray’s arrayWithArray:(resDict’s allValues()))
  (
sortedArray’s sortUsingSelector:"compare:")
  
set aRes to (last item of (sortedArray as list)) –末尾のアイテムが一番値の大きい数値
  
set resKey to first item of ((resDict’s allKeysForObject:aRes) as list) –最大値をもとにKeyを求める
  
  
–可能性の高いものだけ処理してみる(自信たっぷりに全力で間違えることもけっこうある)
  
if (aRes as real) > 0.8 then
    if resKey begins with "efsf" then
      –efsf & efsf_stolen
      
set aRes to (my copyFileAt:aFile toFolder:efsfFol)
    else
      –zeon & zeon_stolen
      
set aRes to (my copyFileAt:aFile toFolder:zeonFol)
    end if
  end if
end repeat

–POSIX path listから指定UTIに含まれるものをPOSIX pathのリストで返す
on filterPOSIXpathListByUTI(aList, targUTI)
  set newList to {}
  
repeat with i in aList
    set j to contents of i
    
set tmpUTI to my retUTIfromPath(j)
    
set utiRes to my filterUTIList({tmpUTI}, targUTI)
    
if utiRes is not equal to {} then
      set the end of newList to j
    end if
  end repeat
  
return newList
end filterPOSIXpathListByUTI

–指定のPOSIX pathのファイルのUTIを求める
on retUTIfromPath(aPOSIXPath)
  set aURL to |NSURL|’s fileURLWithPath:aPOSIXPath
  
set {theResult, theValue} to aURL’s getResourceValue:(reference) forKey:NSURLTypeIdentifierKey |error|:(missing value)
  
  
if theResult = true then
    return theValue as string
  else
    return theResult
  end if
end retUTIfromPath

–UTIリストが指定UTIに含まれているかどうか演算を行う
on filterUTIList(aUTIList, aUTIstr)
  set anArray to NSArray’s arrayWithArray:aUTIList
  
set aPred to NSPredicate’s predicateWithFormat_("SELF UTI-CONFORMS-TO %@", aUTIstr)
  
set bRes to (anArray’s filteredArrayUsingPredicate:aPred) as list
  
return bRes
end filterUTIList

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

on copyFileAt:POSIXPath toFolder:folderPath
  set POSIXPath to NSString’s stringWithString:POSIXPath
  
set folderPOSIXPath to NSString’s stringWithString:folderPath
  
set theName to POSIXPath’s lastPathComponent()
  
set newPath to folderPOSIXPath’s stringByAppendingPathComponent:theName
  
set fileManager to 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 

Posted in file File path Image list Machine Learning Record | Tagged 10.14savvy NSArray NSFileManager NSMutableArray NSPredicate NSSortDescriptor NSString NSURL NSURLTypeIdentifierKey | 1 Comment

squeezeNetで画像のオブジェクト認識を行い、結果をCSV出力してNumbersでオープン

Posted on 11月 12, 2018 by Takaaki Naganoya

Core MLモデル「squeezeNet.mlmodel」を用いて画像のオブジェクト認識を行い、結果をデスクトップにCSV形式でデータ書き出しを行い、NumbersでオープンするAppleScriptです。

–> Download squeezeNetKit.framework (To ~/Library/Frameworks)

macOS 10.13上ではスクリプトエディタ、Script Debugger上で、macOS 10.14上ではScript Debugger上で動作します(AppleScript Appletでも動作)。

さまざまなCore MLモデルを用いて画像認識を行わせてはみたものの、最もスコアの高いもののラベルだけを返させても、結果がまったく内容にそぐわないケースが多く見られます。


▲機械学習の評価値が1.0に近いものは確度が高く、0.0に近いものは判断に迷った末に消極的に出した値。経験則でいえば、最高スコアの項目が0.7以上のスコアでないといまひとつのようです(0.7以上でもダメな時はダメなわけで、、、)

画像認識については、結果があまりに的外れで「なんじゃこら?」というケースが多いので、確認のためにCSVデータ出力させてNumbers上で確認させてみることにしてみました。そのためにありあわせのサブルーチンを組み合わせて作ったものです。

スコアが高い順に結果を並べて確認してみると、最も高いスコアの値であっても0.0に近い評価値であることがあり、「何も考えずに最高スコアのラベルだけ取ってくるのはものすごく危険」なことがよくわかります。


▲squeezeNet.mlmodelをNetronでビジュアライズしたところ(一部抜粋)

AppleScript名:SqueezeNetでオブジェクト判定して結果をCSV書き出ししてNumbersでオープン.scpt
–  Created by: Takaaki Naganoya
–  Created on: 2018/11/11
–  Copyright © 2018 Piyomaru Software, All Rights Reserved
use AppleScript version "2.7" — High Sierra (10.13) or later
use framework "Foundation"
use framework "AppKit"
use framework "squeezeNetKit"
use scripting additions

property NSUUID : a reference to current application’s NSUUID
property NSArray : a reference to current application’s NSArray
property NSImage : a reference to current application’s NSImage
property NSMutableArray : a reference to current application’s NSMutableArray
property NSSortDescriptor : a reference to current application’s NSSortDescriptor

set aFile to POSIX path of (choose file of type {"public.image"})
set aImage to NSImage’s alloc()’s initWithContentsOfFile:aFile
set resDict to (current application’s imageClassifier’s alloc()’s init()’s ImageClassifierWithImageAndRetDict:aImage)

set kArray to (resDict’s allKeys())
set vArray to (resDict’s allValues())
set aLen to kArray’s |count|()

set anArray to NSMutableArray’s new()

repeat with i from 0 to (aLen – 1)
  set tmpKey to (kArray’s objectAtIndex:i) as string
  
set tmpVal to (vArray’s objectAtIndex:i) as real
  (
anArray’s addObject:{aKey:tmpKey, aVal:tmpVal})
end repeat

–Sort by value
set bList to sortRecListByLabel(anArray, "aVal", false) of me

–record in list –> 2D list
set cList to convRecInListTo2DList(bList) of me

–デスクトップにCSVファイルを書き出してNumbersでオープン
set aPath to (((path to desktop) as string) & (NSUUID’s UUID()’s UUIDString()) as string) & ".csv"
set fRes to saveAsCSV(cList, aPath) of me
tell application "Numbers"
  open (aPath as alias)
end tell

–リストに入れたレコードを、指定の属性ラベルの値でソート
on sortRecListByLabel(aRecList as list, aLabelStr as string, ascendF as boolean)
  set aArray to NSArray’s arrayWithArray:aRecList
  
set sortDesc to NSSortDescriptor’s alloc()’s initWithKey:aLabelStr ascending:ascendF
  
set sortDescArray to NSArray’s arrayWithObjects:sortDesc
  
set sortedArray to aArray’s sortedArrayUsingDescriptors:sortDescArray
  
set bList to (sortedArray) as anything
  
return bList
end sortRecListByLabel

on convRecInListTo2DList(aList)
  set anArray to NSMutableArray’s arrayWithArray:aList
  
set fItem to anArray’s objectAtIndex:0
  
set keyList to fItem’s allKeys() as list
  
set aCount to anArray’s |count|()
  
  
set allArray to NSMutableArray’s new()
  
  
repeat with i from 1 to aCount
    set anItem to (anArray’s objectAtIndex:(i – 1))
    
set tmpItem to {}
    
    
repeat with ii in keyList
      set tmpDat to (anItem’s valueForKeyPath:(contents of ii))
      
if tmpDat is not equal to missing value then
        set the end of tmpItem to tmpDat
      else
        set the end of tmpItem to ""
      end if
    end repeat
    (
allArray’s addObject:tmpItem)
  end repeat
  
  
set allList to allArray as list
  
set the beginning of allList to keyList
  
return allList
end convRecInListTo2DList

–CSV Kit

–2D List to CSV file
on saveAsCSV(aList, aPath)
  –set crlfChar to (ASCII character 13) & (ASCII character 10)
  
set crlfChar to (string id 13) & (string id 10)
  
set LF to (string id 10)
  
set wholeText to ""
  
  
repeat with i in aList
    set newLine to {}
    
    
–Sanitize (Double Quote)
    
repeat with ii in i
      set jj to ii as text
      
set kk to repChar(jj, string id 34, (string id 34) & (string id 34)) of me –Escape Double Quote
      
set the end of newLine to kk
    end repeat
    
    
–Change Delimiter
    
set aLineText to ""
    
set curDelim to AppleScript’s text item delimiters
    
set AppleScript’s text item delimiters to "\",\""
    
set aLineList to newLine as text
    
set AppleScript’s text item delimiters to curDelim
    
    
set aLineText to repChar(aLineList, return, "") of me –delete return
    
set aLineText to repChar(aLineText, LF, "") of me –delete lf
    
    
set wholeText to wholeText & "\"" & aLineText & "\"" & crlfChar –line terminator: CR+LF
  end repeat
  
  
if (aPath as string) does not end with ".csv" then
    set bPath to aPath & ".csv" as Unicode text
  else
    set bPath to aPath as Unicode text
  end if
  
  
write_to_file(wholeText, bPath, false) of me
  
end saveAsCSV

on write_to_file(this_data, target_file, append_data)
  tell current application
    try
      set the target_file to the target_file as text
      
set the open_target_file to open for access file target_file with write permission
      
if append_data is false then set eof of the open_target_file to 0
      
write this_data to the open_target_file starting at eof
      
close access the open_target_file
      
return true
    on error error_message
      try
        close access file target_file
      end try
      
return error_message
    end try
  end tell
end write_to_file

on repChar(origText as text, targChar as text, repChar as text)
  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 

Posted in file Image list Machine Learning Record Sort | Tagged 10.13savvy 10.14savvy NSArray NSImage NSMutableArray NSSortDescriptor NSUUID Numbers | Leave a comment

iTunesライブラリ中の各trackの種別を集計(media種別)

Posted on 10月 24, 2018 by Takaaki Naganoya

iTunesライブラリ(+Apple Book)のメディアの種別(mediaKind)を集計するAppleScriptです。

iTunesLibrary.framework経由でメデイアアイテムの情報にアクセスするため、iTunes.appが起動していてもいなくても関係ありません。開発環境のマシン(MacBook Pro Retina 2012 Core i7 2.66GHz)でiTunesに6,871曲の楽曲が存在している状態で、集計に0.01秒程度です。

ITMediaItemのmediaKindを取得して種別情報を取り出しました。「楽曲のみ抽出」といった用途にバッチリ向いているようです。

iTunes.appとプロセス間通信していないため、macOS 10.14でSecurityダイアログが表示されることもありません。

AppleScript名:iTunesライブラリ中の各trackの種別を集計(media種別).scptd
— Created 2018-10-16 by Takaaki Naganoya
— 2018 Piyomaru Software
use AppleScript version “2.4”
use scripting additions
use framework “Foundation”
use framework “iTunesLibrary”
(*
ITLibMediaItemMediaKindAlertTone:21
ITLibMediaItemMediaKindAudiobook:5
ITLibMediaItemMediaKindBook:19
ITLibMediaItemMediaKindDigitalBooklet:15
ITLibMediaItemMediaKindHomeVideo:12
ITLibMediaItemMediaKindIOSApplication:16
ITLibMediaItemMediaKindInteractiveBooklet:9
ITLibMediaItemMediaKindMovie:3
ITLibMediaItemMediaKindMusicVideo:7
ITLibMediaItemMediaKindPDFBook:20
ITLibMediaItemMediaKindPDFBooklet:6
ITLibMediaItemMediaKindPodcast:4
ITLibMediaItemMediaKindRingtone:14
ITLibMediaItemMediaKindSong:2
ITLibMediaItemMediaKindTVShow:8
ITLibMediaItemMediaKindUnknown:1
ITLibMediaItemMediaKindVoiceMemo:17
ITLibMediaItemMediaKindiTunesU:18
*)

property NSDictionary : a reference to current application’s NSDictionary
property NSCountedSet : a reference to current application’s NSCountedSet
property NSMutableArray : a reference to current application’s NSMutableArray
property NSSortDescriptor : a reference to current application’s NSSortDescriptor


set library to current application’s ITLibrary’s libraryWithAPIVersion:“1.0” |error|:(missing value)
if library is equal to missing value then return


set playLists to library’s allPlaylists()
set gList to (library’s allMediaItems())’s mediaKind
set aRes to countItemsByItsAppearance(gList) of me
–> {{theName:2, numberOfTimes:7010}, {theName:4, numberOfTimes:1422}, {theName:19, numberOfTimes:57}, {theName:3, numberOfTimes:25}, {theName:12, numberOfTimes:21}, {theName:5, numberOfTimes:5}, {theName:6, numberOfTimes:5}, {theName:7, numberOfTimes:2}, {theName:20, numberOfTimes:1}}


–出現回数で集計
on countItemsByItsAppearance(aList)
  set aSet to NSCountedSet’s alloc()’s initWithArray:aList
set bArray to NSMutableArray’s array()
set theEnumerator to aSet’s objectEnumerator()


  repeat
    set aValue to theEnumerator’s nextObject()
if aValue is missing value then exit repeat
bArray’s addObject:(NSDictionary’s dictionaryWithObjects:{aValue, (aSet’s countForObject:aValue)} forKeys:{“theName”, “numberOfTimes”})
  end repeat


  –出現回数(numberOfTimes)で降順ソート
set theDesc to NSSortDescriptor’s sortDescriptorWithKey:“numberOfTimes” ascending:false
bArray’s sortUsingDescriptors:{theDesc}


  return bArray as list
end countItemsByItsAppearance


★Click Here to Open This Script 
Posted in list | Tagged 10.11savvy 10.12savvy 10.13savvy 10.14savvy ITLibrary iTunes NSCountedSet NSDictionary NSMutableArray NSSortDescriptor | Leave a comment

iTunesライブラリ中の各trackの種別を集計(ファイル種別文字列)

Posted on 10月 24, 2018 by Takaaki Naganoya

iTunesライブラリ(+Apple Book)のメディアの種別(kind)を集計するAppleScriptです。

iTunesLibrary.framework経由でメデイアアイテムの情報にアクセスするため、iTunes.appが起動していてもいなくても関係ありません。開発環境のマシン(MacBook Pro Retina 2012 Core i7 2.66GHz)でiTunesに6,871曲の楽曲が存在している状態で、集計に0.01秒程度です。

ITMediaItemのkindを取得して種別情報を取り出してみたものの、この文字列情報がローカライズされているのと、あくまでファイル属性の情報を返すため、「楽曲のみ抽出」といった用途には向いていません。

iTunes.appとプロセス間通信していないため、macOS 10.14でSecurityダイアログが表示されることもありません。

AppleScript名:iTunesライブラリ中の各trackの種別を集計(ファイル種別文字列).scptd
— Created 2018-10-16 by Takaaki Naganoya
— 2018 Piyomaru Software
use AppleScript version “2.4”
use scripting additions
use framework “Foundation”
use framework “iTunesLibrary”

 

 

 

set library to current application’s ITLibrary’s libraryWithAPIVersion:“1.0” |error|:(missing value)
if library is equal to missing value then return

set playLists to library’s allPlaylists()
set gList to (library’s allMediaItems())’s |kind| –kind is *localized*!!
set aRes to countItemsByItsAppearance(gList) of me
–> {{theName:”AAC オーディオファイル”, numberOfTimes:3260}, {theName:”MPEG オーディオファイル”, numberOfTimes:1760}, {theName:”購入した AAC オーディオファイル”, numberOfTimes:1302}, {theName:”保護された AAC オーディオファイル”, numberOfTimes:1102}, {theName:”MPEGオーディオファイル”, numberOfTimes:421}, {theName:”保護された MPEG-4 オーディオストリーム”, numberOfTimes:134}, {theName:”購入したAACオーディオファイル”, numberOfTimes:124}, {theName:missing value, numberOfTimes:111}, {theName:”MPEG オーディオストリーム”, numberOfTimes:88}, {theName:”MPEGオーディオストリーム”, numberOfTimes:40}, {theName:”Apple ロスレス・オーディオファイル”, numberOfTimes:36}, {theName:”QuickTime ムービーファイル”, numberOfTimes:34}, {theName:”保護されたブック”, numberOfTimes:32}, {theName:”AIFF オーディオファイル”, numberOfTimes:21}, {theName:”購入したブック”, numberOfTimes:21}, {theName:”MPEG-4 ビデオファイル”, numberOfTimes:16}, {theName:”保護された MPEG-4 ビデオファイル”, numberOfTimes:12}, {theName:”PDF 書類”, numberOfTimes:6}, {theName:”AACオーディオファイル”, numberOfTimes:5}, {theName:”ブック”, numberOfTimes:4}, {theName:”購入したMPEG-4ビデオファイル”, numberOfTimes:4}, {theName:”インターネットオーディオストリーム”, numberOfTimes:3}, {theName:”購入した MPEG-4 ビデオファイル”, numberOfTimes:3}, {theName:”WAV オーディオファイル”, numberOfTimes:2}, {theName:”iTunes Extras”, numberOfTimes:2}, {theName:”QuickTimeムービーURL”, numberOfTimes:2}, {theName:”QuickTime ムービー URL”, numberOfTimes:1}, {theName:”MPEG-4ビデオファイル”, numberOfTimes:1}, {theName:”Purchased AAC audio file”, numberOfTimes:1}}

–出現回数で集計
on countItemsByItsAppearance(aList)
  set aSet to current application’s NSCountedSet’s alloc()’s initWithArray:aList
  
set bArray to current application’s NSMutableArray’s array()
  
set theEnumerator to aSet’s objectEnumerator()

  repeat
    set aValue to theEnumerator’s nextObject()
    
if aValue is missing value then exit repeat
    
bArray’s addObject:(current application’s NSDictionary’s dictionaryWithObjects:{aValue, (aSet’s countForObject:aValue)} forKeys:{“theName”, “numberOfTimes”})
  end repeat

  –出現回数(numberOfTimes)で降順ソート
  
set theDesc to current application’s NSSortDescriptor’s sortDescriptorWithKey:“numberOfTimes” ascending:false
  
bArray’s sortUsingDescriptors:{theDesc}

  return bArray as list
end countItemsByItsAppearance

 

★Click Here to Open This Script 

 

Posted in list | Tagged 10.11savvy 10.12savvy 10.13savvy 10.14savvy iTunes NSCountedSet NSDictionary NSMutableArray NSSortDescriptor | Leave a comment

easyJParse v3

Posted on 9月 27, 2018 by Takaaki Naganoya

簡易的な日本語テキストのParse(辞書なし)を行うAppleScriptです。

英語などの言語では、文章中の各単語の間にスペース(” “)を入れるようになっており、

My name is Takaaki Naganoya.

文章を単語ごとに分割することがきわめて容易です。

words of "My name is Takaaki Naganoya."
--> {"My", "name", "is", "Takaaki", "Naganoya"}

一方、日本語の文章において単語は続けて記述するため、

私の名前は長野谷です。

これを単語ごとに切り分けるのは大変です。そのため、単語の辞書を手掛かりに文章中の単語を切り分けるのが普通です。

辞書を使って単語単位の切り分けを行う日本語形態素解析器

日本語テキストを単語(形態素)ごとに区分けするソフトウェアは日本語形態素解析器と呼ばれます。Chasen、Juman、MeCabなどが有名です。形態素解析のための巨大な辞書を用いて、地名ぐらいの固有名詞なら問題なくParseできることが普通です。各単語がどの品詞なのか、活用形はどうなっているかといった文法的な情報も管理しています。

たとえば、ApitoreのREST API経由でKuromojiを呼び出して形態素解析を行うと、

"警告音「Basso」を最大音量で鳴らす"
-->{{startTime:"1538006762864", tokens:{{partOfSpeechLevel1:"名詞", baseForm:"警告", pronunciation:"ケイコク", position:0, partOfSpeechLevel3:"*", reading:"ケイコク", surface:"警告", known:true, allFeatures:"名詞,サ変接続,*,*,*,*,警告,ケイコク,ケイコク", conjugationType:"*", partOfSpeechLevel2:"サ変接続", conjugationForm:"*", allFeaturesArray:{"名詞", "サ変接続", "*", "*", "*", "*", "警告", "ケイコク", "ケイコク"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"名詞", baseForm:"音", pronunciation:"オン", position:2, partOfSpeechLevel3:"一般", reading:"オン", surface:"音", known:true, allFeatures:"名詞,接尾,一般,*,*,*,音,オン,オン", conjugationType:"*", partOfSpeechLevel2:"接尾", conjugationForm:"*", allFeaturesArray:{"名詞", "接尾", "一般", "*", "*", "*", "音", "オン", "オン"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"記号", baseForm:"「", pronunciation:"「", position:3, partOfSpeechLevel3:"*", reading:"「", surface:"「", known:true, allFeatures:"記号,括弧開,*,*,*,*,「,「,「", conjugationType:"*", partOfSpeechLevel2:"括弧開", conjugationForm:"*", allFeaturesArray:{"記号", "括弧開", "*", "*", "*", "*", "「", "「", "「"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"名詞", baseForm:"Basso", pronunciation:"バッソ", position:4, partOfSpeechLevel3:"一般", reading:"バッソ", surface:"Basso", known:true, allFeatures:"名詞,固有名詞,一般,*,*,*,Basso,バッソ,バッソ", conjugationType:"*", partOfSpeechLevel2:"固有名詞", conjugationForm:"*", allFeaturesArray:{"名詞", "固有名詞", "一般", "*", "*", "*", "Basso", "バッソ", "バッソ"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"記号", baseForm:"」", pronunciation:"」", position:9, partOfSpeechLevel3:"*", reading:"」", surface:"」", known:true, allFeatures:"記号,括弧閉,*,*,*,*,」,」,」", conjugationType:"*", partOfSpeechLevel2:"括弧閉", conjugationForm:"*", allFeaturesArray:{"記号", "括弧閉", "*", "*", "*", "*", "」", "」", "」"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"助詞", baseForm:"を", pronunciation:"ヲ", position:10, partOfSpeechLevel3:"一般", reading:"ヲ", surface:"を", known:true, allFeatures:"助詞,格助詞,一般,*,*,*,を,ヲ,ヲ", conjugationType:"*", partOfSpeechLevel2:"格助詞", conjugationForm:"*", allFeaturesArray:{"助詞", "格助詞", "一般", "*", "*", "*", "を", "ヲ", "ヲ"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"名詞", baseForm:"最大", pronunciation:"サイダイ", position:11, partOfSpeechLevel3:"*", reading:"サイダイ", surface:"最大", known:true, allFeatures:"名詞,一般,*,*,*,*,最大,サイダイ,サイダイ", conjugationType:"*", partOfSpeechLevel2:"一般", conjugationForm:"*", allFeaturesArray:{"名詞", "一般", "*", "*", "*", "*", "最大", "サイダイ", "サイダイ"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"名詞", baseForm:"音量", pronunciation:"オンリョー", position:13, partOfSpeechLevel3:"*", reading:"オンリョウ", surface:"音量", known:true, allFeatures:"名詞,一般,*,*,*,*,音量,オンリョウ,オンリョー", conjugationType:"*", partOfSpeechLevel2:"一般", conjugationForm:"*", allFeaturesArray:{"名詞", "一般", "*", "*", "*", "*", "音量", "オンリョウ", "オンリョー"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"助詞", baseForm:"で", pronunciation:"デ", position:15, partOfSpeechLevel3:"一般", reading:"デ", surface:"で", known:true, allFeatures:"助詞,格助詞,一般,*,*,*,で,デ,デ", conjugationType:"*", partOfSpeechLevel2:"格助詞", conjugationForm:"*", allFeaturesArray:{"助詞", "格助詞", "一般", "*", "*", "*", "で", "デ", "デ"}, partOfSpeechLevel4:"*"}, {partOfSpeechLevel1:"動詞", baseForm:"鳴らす", pronunciation:"ナラス", position:16, partOfSpeechLevel3:"*", reading:"ナラス", surface:"鳴らす", known:true, allFeatures:"動詞,自立,*,*,五段・サ行,基本形,鳴らす,ナラス,ナラス", conjugationType:"五段・サ行", partOfSpeechLevel2:"自立", conjugationForm:"基本形", allFeaturesArray:{"動詞", "自立", "*", "*", "五段・サ行", "基本形", "鳴らす", "ナラス", "ナラス"}, partOfSpeechLevel4:"*"}}, endTime:"1538006762864", |log|:"", processTime:"0"}}

のようになります。これらのデータのうち、surface項目を抽出すると、

--> {"警告", "音", "「", "Basso", "」", "を", "最大", "音量", "で", "鳴らす", "。"}

となります。

辞書を使わずにトリッキーな方法で単語単位の切り分けを行う日本語パーサー

一方で、これらの日本語形態素解析器ほどの大規模なデータや機能が必要ない場合もあります。形態素解析のための辞書を持たず、単にそれっぽく単語ごとに区切ることができればよいという、「割り切った用途」に用いるもので、便宜上「日本語パーサー」と呼びます。単語っぽいものに分割することが目的であり、品詞のデータなどは取得できないのが普通です。

この種類のソフトウェアは、工藤 拓さんのTinySegmenterがあり、これをObjective-Cに移植したSuper compact Japanese tokenizer 「Tiny Segmenter」をCocoa Framework化してAppleScriptから呼び出し、テストしています。正規表現を用いて助詞などをピックアップして、それを手掛かりに単語切り分けを行うもので、そのサイズからは想像できないぐらいまっとうに単語に切り分けてくれます。

このTiny Segmenter(Objective-C版)をコマンド解釈用に使ってみたのですが、

--> {"警告音", "「Basso", "」", "を", "最大", "音量", "で", "鳴ら", "す"}

記号などがきちんと分離されなかったため、いまひとつ。自分でコマンド解釈用のParserを作ってみることにしました。

words ofの不完全さを補う簡易日本語パーサーeasyJParse

AppleScriptの「words of」は、前述のように英文であればスペースを区切り子として、文章の単語への分解を行ってくれます。

一方、日本語テキストに対して「words of」で単語分解処理を行うと、ながらく「文字種別の切り替え箇所で区切る」という気の狂ったような使えない処理が行われていました。その無意味さと使えなさをAppleのエンジニアにことあるごとに説明してきたのですが、一向に理解されず、相手にされてきませんでした。

# 冗談抜きで、Appleのエンジニアとは「戦いの歴史」しかありません。そして、そうして戦って勝ち取っていかないと機能の改善もバグの修正も何もないのであります(本当)

風向きが変わってきたのは、OS X 10.6のころ。このころから日本語テキストのwords ofの実行結果が形態素解析を行なっているような気がする動作を行うようになっており、何かに使えるような気がするものの……

 words of "警告音「Basso」を最大音量で鳴らす。"
 --> {"警告", "音", "Basso", "を", "最大", "音量", "で", "鳴らす"}

なぜか記号類などをすべて無視してしまうので、いまひとつ実用性がありませんでした。

そこで、基本的にはこの「words of」の演算結果を活かしつつ、オリジナルの文章と比較を行なって、欠損した記号類を補うことで簡易日本語parserとして利用できるのでは? と考えました。

set aTargName to "警告音「Basso」を最大音量で鳴らす。"
set aList to parseJ(aTargName) of me
--> {"警告", "音", "「", "Basso", "」", "を", "最大", "音量", "で", "鳴らす", "。"}

実際に作ってテストしてみたところ、自分が必要なコマンド解析ぐらいの目的には十分に使えることがわかりました。むしろ、単語切り分けについてはKuromojiと同じ結果が得られています。

しかも、辞書を持たないためコンパクトであり、実行速度もたいへんに高速で、このeasyJParseを組み込んだプログラムはREST APIの日本語形態素解析器を呼んだバージョンよりも明らかに高速化され、ネットワーク接続のない環境でも実行可能になりました。いいことづくめです。

easyJParseの制約事項

なお、easyJParseはすでに文章単位で分割されたテキストをコマンド解釈用に分解するため「だけ」に作ったものであり、長文を文章ごとに分割する機能は持っていません。別のプログラムやルーチンで文章ごとに分割してからeasyJParseで処理してください。

easyJParseは、日本語ユーザー環境における日本語テキストに対する「words of」の演算結果を利用しており、言語環境が日本語に設定していない環境で同様に演算できることは保証していません。
→ 一応、英語ユーザー環境で実行してみたら期待どおりの動作を行いました

当然のことながら、macOS専用です。一部Cocoaの機能を呼び出しているため、macOS 10.10以降で動きます(10.10では動作確認していませんけれども)。

AppleScript名:easyJParse v3
— Created 2018-09-26 by Takaaki Naganoya
— 2018 Piyomaru Software
use AppleScript version "2.5" — El Capitan (10.11) or later
use framework "Foundation"
use scripting additions

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

set aTargName to "警告音「Basso」を最大音量で鳴らす。"
set aList to parseJ(aTargName) of me
–> {"警告", "音", "「", "Basso", "」", "を", "最大", "音量", "で", "鳴らす", "。"}

–set aTargName to "JPEGファイルを50%にリサイズして、デスクトップの「AAA」フォルダに出力"
–set aList to parseJ(aTargName) of me
–> {"JPEG", "ファイル", "を", "50", "%", "に", "リサイズ", "し", "て", "、", "デスクトップ", "の", "「", "AAA", "」", "フォルダ", "に", "出力"}

on parseJ(aTargStr as string)
  copy aTargStr to tStr
  
  
set cList to characters of tStr
  
set wList to words of tStr
  
  
set cLen to length of cList
  
  
set w2List to {}
  
set w3List to {}
  
set aCount to 0
  
  
set lastPos to 0
  
  
repeat with i in wList
    set j to contents of i
    
    
set anOffset to offset of j in tStr
    
    
if anOffset is not equal to 1 then
      set aChar to character (lastPos + 1) of aTargStr
      
      
set the end of w3List to {wordList:aChar, characterList:{aChar}, startPos:(lastPos + 1), endPos:(lastPos + 1)}
    end if
    
    
set aLen to length of j
    
    
set w2List to w2List & (characters of j)
    
set startPointer to (anOffset + aCount)
    
set endPointer to (anOffset + aCount + aLen – 1)
    
    
set the end of w3List to {wordList:j, characterList:(characters of j), startPos:startPointer, endPos:endPointer}
    
    
set trimStart to (anOffset + aLen)
    
    
if trimStart > (length of tStr) then
      set trimStart to 1
    end if
    
    
set tStr to text trimStart thru -1 of tStr
    
    
set aCount to aCount + anOffset + aLen – 1
    
copy endPointer to lastPos
  end repeat
  
  
–句読点など。文末の処理
  
if endPointer is not equal to cLen then
    set the end of w3List to {wordList:tStr, characterList:(characters of tStr), startPos:(lastPos + aCount), endPos:aLen}
  end if
  
  
set bArray to sortRecListByLabel((w3List), "startPos", true) of me
  
set cArray to (bArray’s valueForKeyPath:"wordList") as list
  
  
return cArray
end parseJ

–リストに入れたレコードを、指定の属性ラベルの値でソート
on sortRecListByLabel(aRecList as list, aLabelStr as string, ascendF as boolean)
  set aArray to NSArray’s arrayWithArray:aRecList
  
set sortDesc to NSSortDescriptor’s alloc()’s initWithKey:aLabelStr ascending:ascendF
  
set sortDescArray to NSArray’s arrayWithObjects:sortDesc
  
set sortedArray to aArray’s sortedArrayUsingDescriptors:sortDescArray
  
return sortedArray
end sortRecListByLabel

★Click Here to Open This Script 

Posted in Natural Language Processing Record Sort Text | Tagged 10.11savvy 10.12savvy 10.13savvy NSArray NSSortDescriptor | 7 Comments

Google Drive File StreamのドライブでSpotlight Indexを生成

Posted on 8月 4, 2018 by Takaaki Naganoya

Google Drive File Stream上でSpotlight Indexを生成するAppleScriptです。

Google Driveには、Gmailのオマケでついてくる個人のデバイス間で情報のシンクロを行う目的のPersonal版(DropboxのGoogle版的なもの)と、企業内やグループ内でファイル共有を行うG SuiteのGoodle Drive File Streamがあり、後者についてはGoogleによる「Google Drive File Stream」ツールが提供され、GoogleDriveをデスクトップにマウントできます。

実際にGoogle Drive File Streamを使ってみたところ、デフォルト状態だとSpotlight検索が効きませんでした。

そこで、Index生成してみたものの…既存のファイルについてはSpotlight検索できませんでした。Index生成後に、新規追加したファイルについてはSpotlight検索が有効でした。

後日談:ただ、Google Drive File Streamに対してSpotlight Indexを常時生成するようにすると、CPUに対する負荷が大きくなるため、実際の運用ではSpotlightを無効にして運用するようにしました。

AppleScript名:Google Drive File Streamでインデックス生成
do shell script "mdutil -E /Volumes/GoogleDrive/" with administrator privileges

★Click Here to Open This Script 

自分が試したのは、Google Drive File Stream内の「マイドライブ」領域のみで、

「グループドライブ」領域についてはまだテストできていません。

既存のファイルについては、mdimportコマンドでインデックス作成すれば大丈夫だと思っていますが、実際に試してみると予想外の挙動を行うのかもしれません。

AppleScript名:GDFS上でSpotlight検索
— Created 2016-06-17 by Takaaki Naganoya
— 2016 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

property NSArray : a reference to current application’s NSArray
property NSPredicate : a reference to current application’s NSPredicate
property NSSortDescriptor : a reference to current application’s NSSortDescriptor
property NSMetadataQuery : a reference to current application’s NSMetadataQuery
property NSNotificationCenter : a reference to current application’s NSNotificationCenter
property NSMetadataQueryDidUpdateNotification : a reference to current application’s NSMetadataQueryDidUpdateNotification
property NSMetadataQueryDidFinishGatheringNotification : a reference to current application’s NSMetadataQueryDidFinishGatheringNotification

property searchRes : {}

set origPath to POSIX path of (choose folder)
set aList to spotlightSearch(origPath, "kMDItemContentType == ’public.png’") of me

on spotlightSearch(origPath, aMDfindParam)
  
  
set my searchRes to {} –initialize
  
initiateSearchForFullPath(aMDfindParam, origPath) –Predicate & Scope Directory
  
  
–Waiting for the result
  
repeat while my searchRes = {}
    delay 0.01
  end repeat
  
  
set anObj to my searchRes’s firstObject() –Pick up the first one for test
  
if anObj = missing value then return {} –No Result
  
  
set resArray to {}
  
repeat with anItem in my searchRes
    set j to contents of anItem
    
set aPath to (j’s valueForAttribute:"kMDItemPath") as string
    
set the end of resArray to aPath
  end repeat
  
  
return resArray
  
end spotlightSearch

on initiateSearchForFullPath(aQueryStrings, origPath)
  
  
set aSearch to NSMetadataQuery’s alloc()’s init()
  
  
NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:"queryDidUpdate:" |name|:(NSMetadataQueryDidUpdateNotification) object:aSearch
  
NSNotificationCenter’s defaultCenter()’s addObserver:(me) selector:"initalGatherComplete:" |name|:(NSMetadataQueryDidFinishGatheringNotification) object:aSearch
  
  
set aPredicate to NSPredicate’s predicateWithFormat:aQueryStrings
  
aSearch’s setPredicate:aPredicate
  
  
set aScope to NSArray’s arrayWithObjects:{origPath}
  
aSearch’s setSearchScopes:aScope
  
  
set sortKeys to NSSortDescriptor’s sortDescriptorWithKey:"kMDItemFSName" ascending:true
  
aSearch’s setSortDescriptors:(NSArray’s arrayWithObject:sortKeys)
  
  
aSearch’s startQuery()
  
end initiateSearchForFullPath

on queryDidUpdate:sender
  log sender
  
–> (NSConcreteNotification) NSConcreteNotification 0x7fa618450820 {name = NSMetadataQueryDidFinishGatheringNotification; object = <NSMetadataQuery: 0x7fa6172c6ca0>}
end queryDidUpdate:

on initalGatherComplete:sender
  set anObject to sender’s object
  
anObject’s stopQuery()
  
NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(NSMetadataQueryDidUpdateNotification) object:anObject
  
NSNotificationCenter’s defaultCenter()’s removeObserver:me |name|:(NSMetadataQueryDidFinishGatheringNotification) object:anObject
  
  
set my searchRes to anObject’s results()
end initalGatherComplete:

★Click Here to Open This Script 

Posted in drive file shell script Spotlight | Tagged 10.12savvy NSArray NSMetadataQuery NSNotificationCenter NSPredicate NSSortDescriptor | Leave a comment

PDFから指定ページ以降を削除

Posted on 7月 10, 2018 by Takaaki Naganoya

PDFから指定ページ以降を削除するAppleScriptです。

こんな(↑)418ページもあるPDFから、おためし版の、冒頭から40ページだけ抽出したPDFを作成する際に使用したものです。

もともとは418ページありましたが、、、

実行後は40ページに、、、

AppleScript名:PDFから指定ページ以降を削除
— Modified 2018-07-08 by Takaaki Naganoya
–Original By Shane Stanley
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
use framework "QuartzCore"
use aBplus : script "BridgePlus" –https://www.macosxautomation.com/applescript/apps/BridgePlus.html

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

load framework

set sPage to 41 –ここから末尾までのページを削除
set inFile to (choose file of type {"pdf"} with prompt "Choose your PDF files:")
set pRes to removePDFPageAfter(inFile, sPage) of me

–指定パスのPDFの指定ページ以降をすべて削除
on removePDFPageAfter(inFile, sPage)
  set pCount to pdfPageCount(inFile) of me
  
set eCount to pCount – sPage + 1
  
  
set aindexSet to NSIndexSet’s indexSetWithIndexesInRange:(current application’s NSMakeRange(sPage, eCount))
  
set targPageList to (SMSForder’s arrayWithIndexSet:aindexSet) as list
  
  
return removeSpecificPagesFromPDF(inFile, targPageList) of me
end removePDFPageAfter

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

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

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

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

–指定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 

Posted in file PDF | Tagged 10.11savvy 10.12savvy 10.13savvy NSArray NSIndexSet NSSet NSSortDescriptor NSURL PDFDocument | Leave a comment

Finder上で選択中のMarkdown書類をファイル名で昇順ソート(A→Z)して、各書類のタイトル見出しを取得する

Posted on 7月 8, 2018 by Takaaki Naganoya

Finder上で選択中のMarkdown書類を取得し、ファイル名で昇順ソート(A→Z)して、各Markdown書類中のタイトルのうち最もレベルの高い(重要な)ものを抽出し、まとめてテキストにしてクリップボードに転送するAppleScriptです。

大量にあるMarkdown書類の本文中から一番重要なタイトルを抽出する作業を……さすがに手作業で行うわけには行かなかったので、AppleScriptを書いて処理してみました。ただし、新規に書いた処理はほとんどなく、既存のルーチンの寄せ合わせで構成しています。

Finder上でMarkdown書類を選択した状態で本Scriptを実行すると(Markdown書類以外は除外して)、AppleScriptでMarkdown書類を読み込んで(Markdown書類がUTF-8固定なのでUTF-8を指定して読み込み)、正規表現で書類中のタイトルを抽出し、重要なタイトル(#の個数が少ない)をピックアップ。これをすべての書類について行います。

処理結果をクリップボードに転送します。

最終的には、(手作業で加工して)このようになります。

若干の手作業は発生してしまいますが、このScriptを組まなかったら、とてもその手作業を行う気も起こらなかったわけで、、、

AppleScript名:Finder上で選択中のMarkdown書類をファイル名で昇順ソート(A→Z)して、各書類のタイトル見出しを取得する
— Created 2018-06-26 by Takaaki Naganoya
— 2018 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "Quartz"
use mdLib : script "Metadata Lib" version "1.0.0"
use bPlus : script "BridgePlus" –https://www.macosxautomation.com/applescript/apps/BridgePlus.html

property |NSURL| : a reference to current application’s |NSURL|
property NSArray : a reference to current application’s NSArray
property NSString : a reference to current application’s NSString
property SMSForder : a reference to current application’s SMSForder
property NSIndexSet : a reference to current application’s NSIndexSet
property NSPredicate : a reference to current application’s NSPredicate
property NSMutableSet : a reference to current application’s NSMutableSet
property NSFileManager : a reference to current application’s NSFileManager
property NSCountedSet : a reference to current application’s NSCountedSet
property NSURLPathKey : a reference to current application’s NSURLPathKey
property NSMutableArray : a reference to current application’s NSMutableArray
property NSURLNameKey : a reference to current application’s NSURLNameKey
property NSSortDescriptor : a reference to current application’s NSSortDescriptor
property NSURLIsPackageKey : a reference to current application’s NSURLIsPackageKey
property NSRegularExpression : a reference to current application’s NSRegularExpression
property NSURLIsDirectoryKey : a reference to current application’s NSURLIsDirectoryKey
property NSURLTypeIdentifierKey : a reference to current application’s NSURLTypeIdentifierKey
property NSURLContentModificationDateKey : a reference to current application’s NSURLContentModificationDateKey
property NSRegularExpressionAnchorsMatchLines : a reference to current application’s NSRegularExpressionAnchorsMatchLines
property NSDirectoryEnumerationSkipsHiddenFiles : a reference to current application’s NSDirectoryEnumerationSkipsHiddenFiles
property NSRegularExpressionDotMatchesLineSeparators : a reference to current application’s NSRegularExpressionDotMatchesLineSeparators

load framework

–set inFiles to (choose file of type {"pdf"} with prompt "Choose your PDF files:" with multiple selections allowed)
tell application "Finder"
  set inFiles to selection as alias list
end tell

if inFiles = {} then return

–指定のAlias listのうちMarkdown書類のみ抽出
set filRes1 to filterAliasListByUTI(inFiles, "net.daringfireball.markdown") of me

–指定のPOSIX path listから、各Markdown書類中の一番重要な見出しを抽出して返す
set tRes to listTitlesFromMarkdownDocPathList(filRes1) of me

–取得したタイトル一覧リストをテキストに変換
set t2Res to retStrFromArrayWithDelimiter(tRes, return) of me

–クリップボードに結果を転送
set the clipboard to t2Res

on listTitlesFromMarkdownDocPathList(inFiles)
  set outList to {}
  
set inFilesSorted to my filesInListSortAscending(inFiles)
  
  
repeat with i in inFilesSorted
    –POSIX pathからaliasにパス変換してテキスト読み込み
    
set j to POSIX file (contents of i)
    
set jj to j as alias
    
set aStr to (read jj as «class utf8»)
    
    
set aList to retHeaders(aStr) of me –Markdown書類中の見出しをリストアップ
    
–>  {​​​​​{​​​​​​​1, ​​​​​​​" 2008/3/9 5桁の乱数を生成"​​​​​}​​​}
    
    
if aList is not equal to {} then
      –2D Listの昇順ソート
      
set sortIndexes to {0} –Key Item id: begin from 0
      
set sortOrders to {true} –ascending = true
      
set sortTypes to {"compare:"}
      
set resList to (current application’s SMSForder’s subarraysIn:(aList) sortedByIndexes:sortIndexes ascending:sortOrders sortTypes:sortTypes |error|:(missing value)) as list
      
      
set aCon to contents of second item of first item of resList
      
set the end of outList to aCon
    end if
  end repeat
  
return outList
end listTitlesFromMarkdownDocPathList

on filesInListSortAscending(aliasList as list)
  set cList to {}
  
repeat with i in aliasList
    set j to contents of i
    
set aFileName to ((current application’s NSString’s stringWithString:j)’s valueForKeyPath:"lastPathComponent")
    
set the end of cList to {fileName:aFileName, pathDat:j}
  end repeat
  
  
set aResList to sortRecListByLabel(cList, "fileName", true) of me –昇順ソート
  
set bResList to (aResList’s valueForKeyPath:"pathDat") as list of string or string
  
return bResList
end filesInListSortAscending

–Alias listから指定UTIに含まれるものをPOSIX pathのリストで返す
on filterAliasListByUTI(aList, targUTI)
  set newList to {}
  
repeat with i in aList
    set j to POSIX path of i
    
set tmpUTI to my retUTIfromPath(j)
    
set utiRes to my filterUTIList({tmpUTI}, targUTI)
    
if utiRes is not equal to {} then
      set the end of newList to j
    end if
  end repeat
  
return newList
end filterAliasListByUTI

–指定のPOSIX pathのファイルのUTIを求める
on retUTIfromPath(aPOSIXPath)
  set aURL to |NSURL|’s fileURLWithPath:aPOSIXPath
  
set {theResult, theValue} to aURL’s getResourceValue:(reference) forKey:NSURLTypeIdentifierKey |error|:(missing value)
  
  
if theResult = true then
    return theValue as string
  else
    return theResult
  end if
end retUTIfromPath

–UTIリストが指定UTIに含まれているかどうか演算を行う
on filterUTIList(aUTIList, aUTIstr)
  set anArray to NSArray’s arrayWithArray:aUTIList
  
set aPred to NSPredicate’s predicateWithFormat_("SELF UTI-CONFORMS-TO %@", aUTIstr)
  
set bRes to (anArray’s filteredArrayUsingPredicate:aPred) as list
  
return bRes
end filterUTIList

on returnNumberCharsOnly(aStr)
  set anNSString to current application’s NSString’s stringWithString:aStr
  
set anNSString to anNSString’s stringByReplacingOccurrencesOfString:"[^0-9]" withString:"" options:(current application’s NSRegularExpressionSearch) range:{0, anNSString’s |length|()}
  
return anNSString as text
end returnNumberCharsOnly

–リストに入れたレコードを、指定の属性ラベルの値でソート
on sortRecListByLabel(aRecList as list, aLabelStr as string, ascendF as boolean)
  set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
  
set sortDesc to current application’s NSSortDescriptor’s alloc()’s initWithKey:aLabelStr ascending:ascendF
  
set sortDescArray to current application’s NSArray’s arrayWithObjects:sortDesc
  
set sortedArray to aArray’s sortedArrayUsingDescriptors:sortDescArray
  
return sortedArray
end sortRecListByLabel

–見出し抽出用サブルーチン群
on retHeaders(aCon)
  set tList to {}
  
set regStr to "^#{1,6}[^#]*?$"
  
  
set headerList to my findPattern:regStr inString:aCon
  
repeat with i in headerList
    set j to contents of i
    
set regStr2 to "^#{1,6}[^#]*?"
    
set headerLevel to length of first item of (my findPattern:regStr2 inString:j)
    
set the end of tList to {headerLevel, text (headerLevel + 1) thru -1 in j}
  end repeat
  
  
return tList
end retHeaders

on findPattern:thePattern inString:theString
  set theOptions to ((NSRegularExpressionDotMatchesLineSeparators) as integer) + ((NSRegularExpressionAnchorsMatchLines) as integer)
  
set theRegEx to NSRegularExpression’s regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value)
  
set theFinds to theRegEx’s matchesInString:theString options:0 range:{location:0, |length|:length of theString}
  
set theFinds to theFinds as list — so we can loop through
  
set theResult to {} — we will add to this
  
set theNSString to NSString’s stringWithString:theString
  
repeat with i from 1 to count of items of theFinds
    set theRange to (item i of theFinds)’s range()
    
set end of theResult to (theNSString’s substringWithRange:theRange) as string
  end repeat
  
return theResult
end findPattern:inString:

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

★Click Here to Open This Script 

Posted in file list Markdown regexp Sort Text | Tagged 10.11savvy 10.12savvy 10.13savvy Finder NSArray NSCountedSet NSDirectoryEnumerationSkipsHiddenFiles NSFileManager NSIndexSet NSMutableArray NSMutableSet NSPredicate NSRegularExpression NSRegularExpressionAnchorsMatchLines NSRegularExpressionDotMatchesLineSeparators NSSortDescriptor NSString NSURL NSURLContentModificationDateKey NSURLIsDirectoryKey NSURLIsPackageKey NSURLNameKey NSURLPathKey NSURLTypeIdentifierKey | 3 Comments

CotEditorの最前面のテキストの日付フォーマットを変換する

Posted on 7月 7, 2018 by Takaaki Naganoya

CotEditorでオープン中の最前面の書類中のテキストの、日付フォーマットを変換するAppleScriptです。


▲スクリーンショットは作成中のものです

Blogのアーカイブ本のオマケとして(記事だけだとウンザリする仕上がりだったので)、冒頭にその年に何があったかをAppleのニュースリリースから抽出して掲載してみたのですが、日付の形式が独特だったので、手で打ち直していました。

繰り返して行うと面倒だったので、NSDataDetectorで自然言語テキスト(CotEditorの本文テキスト)から日付を自動検出して書き換えを試みたのですが、NSDataDetectorではこのサンプルのようなほんのちょっとだけ手の加わった日付テキストでは検出してくれませんでした。

一応、フォーマット自体は固定だったので、フォーマッターだけ指定すれば日付テキストとして認識し、常識的なYYYY/MM/DDの日付フォーマットに書き換えるようにしてみました。


▲日付フォーマット文字列の入力


▲日付フォーマット置換したテキスト(処理は一瞬)

ただし、日付フォーマットテキストが行で独立しているもの(添付スクリーンショットのように)を処理対象にしているので、あまり汎用性はなさそうです。

一般的には正規表現で指定することになるんでしょうけれど、その正規表現というかもともとの日付フォーマットを元のテキストから自動検出してくれることがベストだと思います。

なお、CotEditorに依存した処理はひとつもないので、JeditΩでもmiでもテキストエディットでもTextWranglerでもBBEditでも、対象にして処理することはかんたんです。クリップボードの内容に対して処理したほうがいいのかもしれません。

AppleScript名:CotEditorの最前面のテキストの日付フォーマットを変換する
— Created 2018-07-07 by Takaaki Naganoya
— 2018 Piyomaru Software
use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

property NSArray : a reference to current application’s NSArray
property NSString : a reference to current application’s NSString
property NSLocale : a reference to current application’s NSLocale
property NSDictionary : a reference to current application’s NSDictionary
property NSCountedSet : a reference to current application’s NSCountedSet
property NSMutableArray : a reference to current application’s NSMutableArray
property NSSortDescriptor : a reference to current application’s NSSortDescriptor
property NSDateFormatter : a reference to current application’s NSDateFormatter

set tmpArray to NSMutableArray’s new()

–変換元(テキストから自動取得できるとよかったのに)
set dFfromStr to text returned of (display dialog "Input Date Format:(年=yyyy, 月=MM, 日=dd)" default answer "MM月 dd, yyyy")

–変換先
set dOutFormStr to "yyyy/MM/dd"

tell application "CotEditor"
  tell front document
    set aRes to contents of it
  end tell
  
  
set aList to paragraphs of aRes
end tell

repeat with i in aList
  set j to contents of i
  
set theDate to dateFromStringWithDateFormat(j, dFfromStr) of me
  
if theDate is not equal to missing value then
    set newDateStr to convDateObjToStrWithFormat(theDate, dOutFormStr) of me
    (
tmpArray’s addObject:newDateStr)
  else
    (tmpArray’s addObject:j)
  end if
end repeat

–NSArrayを指定デリミタをはさんでテキスト化
set tRes to tmpArray’s componentsJoinedByString:(return)
set ttRes to tRes as string

tell application "CotEditor"
  tell front document
    set (contents of it) to ttRes
  end tell
end tell

–日付文字列からdate objectを作成する
on dateFromStringWithDateFormat(dateString as string, dateFormat as string)
  set dStr to NSString’s stringWithString:dateString
  
set dateFormatStr to NSString’s stringWithString:dateFormat
  
  
set aDateFormatter to NSDateFormatter’s alloc()’s init()
  
aDateFormatter’s setDateFormat:dateFormatStr
  
aDateFormatter’s setLocale:(NSLocale’s alloc()’s initWithLocaleIdentifier:"en_US_POSIX")
  
  
set aDestDate to (aDateFormatter’s dateFromString:dStr)
  
  
return aDestDate as list of string or string
end dateFromStringWithDateFormat

–date objectから指定の日付文字列を作成する
on convDateObjToStrWithFormat(aDateO as date, aFormatStr as string)
  set aDF to NSDateFormatter’s alloc()’s init()
  
  
set aLoc to NSLocale’s currentLocale()
  
set aLocStr to (aLoc’s localeIdentifier()) as string
  
  
aDF’s setLocale:(NSLocale’s alloc()’s initWithLocaleIdentifier:aLocStr)
  
aDF’s setDateFormat:aFormatStr
  
set dRes to (aDF’s stringFromDate:aDateO) as string
  
return dRes
end convDateObjToStrWithFormat

★Click Here to Open This Script 

Posted in Calendar Text | Tagged 10.11savvy 10.12savvy 10.13savvy CotEditor NSArray NSCountedSet NSDateFormatter NSDictionary NSLocale NSMutableArray NSSortDescriptor NSString | Leave a comment

電子書籍(PDF)をオンラインストアで販売中!

Google Search

Popular posts

  • 開発機としてM2 Mac miniが来たのでガチレビュー
  • CotEditorで2つの書類の行単位での差分検出
  • macOS 15, Sequoia
  • 指定のWordファイルをPDFに書き出す
  • Pages本執筆中に、2つの書類モード切り替えに気がついた
  • Numbersで選択範囲のセルの前後の空白を削除
  • メキシカンハットの描画
  • Pixelmator Pro v3.6.4でAppleScriptからの操作時の挙動に違和感が
  • AdobeがInDesign v19.4からPOSIX pathを採用
  • AppleScriptによる並列処理
  • Safariで「プロファイル」機能を使うとAppleScriptの処理に影響
  • Cocoa Scripting Course 続刊計画
  • macOS 14.xでScript Menuの実行速度が大幅に下がるバグ
  • AppleScript入門③AppleScriptを使った「自動化」とは?
  • NaturalLanguage.frameworkでNLEmbeddingの処理が可能な言語をチェック
  • Keynote/Pagesで選択中の表カラムの幅を均等割
  • Keynote、Pages、Numbers Ver.14.0が登場
  • macOS 15 リモートApple Eventsにバグ?
  • デフォルトインストールされたフォント名を取得するAppleScript
  • AppleScript入門① AppleScriptってなんだろう?

Tags

10.11savvy (1101) 10.12savvy (1242) 10.13savvy (1391) 10.14savvy (587) 10.15savvy (438) 11.0savvy (283) 12.0savvy (212) 13.0savvy (193) 14.0savvy (145) 15.0savvy (126) CotEditor (66) Finder (51) iTunes (19) Keynote (116) NSAlert (61) NSArray (51) NSBitmapImageRep (20) NSBundle (20) NSButton (34) NSColor (53) NSDictionary (28) NSFileManager (23) NSFont (21) NSImage (41) NSJSONSerialization (21) NSMutableArray (63) NSMutableDictionary (22) NSPredicate (36) NSRunningApplication (56) NSScreen (30) NSScrollView (22) NSString (119) NSURL (98) NSURLRequest (23) NSUTF8StringEncoding (30) NSView (33) NSWorkspace (20) Numbers (76) Pages (54) Safari (44) Script Editor (27) WKUserContentController (21) WKUserScript (20) WKWebView (23) WKWebViewConfiguration (22)

カテゴリー

  • 2D Bin Packing
  • 3D
  • AirDrop
  • AirPlay
  • Animation
  • AppleScript Application on Xcode
  • Beginner
  • Benchmark
  • beta
  • Bluetooth
  • Books
  • boolean
  • bounds
  • Bug
  • Calendar
  • call by reference
  • check sum
  • Clipboard
  • Cocoa-AppleScript Applet
  • Code Sign
  • Color
  • Custom Class
  • dialog
  • diff
  • drive
  • Droplet
  • exif
  • file
  • File path
  • filter
  • folder
  • Font
  • Font
  • GAME
  • geolocation
  • GUI
  • GUI Scripting
  • Hex
  • History
  • How To
  • iCloud
  • Icon
  • Image
  • Input Method
  • Internet
  • iOS App
  • JavaScript
  • JSON
  • JXA
  • Keychain
  • Keychain
  • Language
  • Library
  • list
  • Locale
  • Localize
  • Machine Learning
  • Map
  • Markdown
  • Menu
  • Metadata
  • MIDI
  • MIME
  • Natural Language Processing
  • Network
  • news
  • Noification
  • Notarization
  • Number
  • Object control
  • OCR
  • OSA
  • parallel processing
  • PDF
  • Peripheral
  • PRODUCTS
  • QR Code
  • Raw AppleEvent Code
  • Record
  • rectangle
  • recursive call
  • regexp
  • Release
  • Remote Control
  • Require Control-Command-R to run
  • REST API
  • Review
  • RTF
  • Sandbox
  • Screen Saver
  • Script Libraries
  • sdef
  • search
  • Security
  • selection
  • shell script
  • Shortcuts Workflow
  • Sort
  • Sound
  • Spellchecker
  • Spotlight
  • SVG
  • System
  • Tag
  • Telephony
  • Text
  • Text to Speech
  • timezone
  • Tools
  • Update
  • URL
  • UTI
  • Web Contents Control
  • WiFi
  • XML
  • XML-RPC
  • イベント(Event)
  • 未分類

アーカイブ

  • 2025年4月
  • 2025年3月
  • 2025年2月
  • 2025年1月
  • 2024年12月
  • 2024年11月
  • 2024年10月
  • 2024年9月
  • 2024年8月
  • 2024年7月
  • 2024年6月
  • 2024年5月
  • 2024年4月
  • 2024年3月
  • 2024年2月
  • 2024年1月
  • 2023年12月
  • 2023年11月
  • 2023年10月
  • 2023年9月
  • 2023年8月
  • 2023年7月
  • 2023年6月
  • 2023年5月
  • 2023年4月
  • 2023年3月
  • 2023年2月
  • 2023年1月
  • 2022年12月
  • 2022年11月
  • 2022年10月
  • 2022年9月
  • 2022年8月
  • 2022年7月
  • 2022年6月
  • 2022年5月
  • 2022年4月
  • 2022年3月
  • 2022年2月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年10月
  • 2021年9月
  • 2021年8月
  • 2021年7月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2020年7月
  • 2020年6月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年12月
  • 2019年11月
  • 2019年10月
  • 2019年9月
  • 2019年8月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年11月
  • 2018年10月
  • 2018年9月
  • 2018年8月
  • 2018年7月
  • 2018年6月
  • 2018年5月
  • 2018年4月
  • 2018年3月
  • 2018年2月

https://piyomarusoft.booth.pm/items/301502

メタ情報

  • ログイン
  • 投稿フィード
  • コメントフィード
  • WordPress.org

Forum Posts

  • 人気のトピック
  • 返信がないトピック

メタ情報

  • ログイン
  • 投稿フィード
  • コメントフィード
  • WordPress.org
Proudly powered by WordPress
Theme: Flint by Star Verte LLC