FileMaker Pro 2024(v21)がv21.1.1にアップデートしました。
macOS 12.xのサポートが打ち切られ、macOS 13以降が対象となっています。
FileMaker Pro v21.1.1のAppleScript用語辞書に、とくに変更は加えられていません。
GUIアプリを作ったときに、ステータスメニュー上に置いたアイコン>メニューからアプリ本体のウィンドウをアクティブにできないという問題が発生しています。
macOS 13、14、15とずーーっとこの動作なので、全世界的に困らされている状況があって……久しぶりにXcodeでGUIアプリをAppleScriptで作って、この問題に例外なく自分も悩まされていました。
その解決を行ったAppleScriptプロジェクトです。macOS 13.7.1上でXcode 15.2で試しています。
# 本記事投稿後にプログラムの見直しを行い、掲載リストおよびXcodeプロジェクトの差し替えを行いました。
GUIアプリ内から生成したステータスバー上のステータスアイテム。ステータスアイテムからメニュー表示。表示されたメニューから項目選択でメインウィンドウを最前面に表示する。
tell current application to activate
では何も起こりません(macOS 12まではこれで大丈夫だった)。
そういう操作を行なってもメインウィンドウが最前面に表示されず、メインウィンドウを強制的にアクティブにするようなコードを入れても、「UIがないプロセスから他のプロセスをアクティブにできない」といったワーニングが表示されるだけで、何も起こりません。
メチャクチャ困ります。
そこで、edama2さんに相談していろいろ試してみたところ、強制的にGUIアプリを最前面に持っていけました。
こちらの議論を参考にしています。
on activateMe:aSender
theWindow's makeKeyAndOrderFront:(me)
theWindow's orderFrontRegardless()
end activateMe:
当初、ウィンドウの生成を動的に行なっていましたが、その部分を削除しても動作することを確認できたため、削除しています。
なお、macOS 15.2+Xcode 16.1でビルドして実行してみたら、「tell current application to activate」だけでウィンドウを最前面に持っていけています。ここで紹介している処理は、macOS 13、14で同様の処理ができるように必要になってしまうようです。
macOS 13/14(+15.0〜15.1?)のバグと言って差し支えないでしょう。
AppleScript名:AppDelegate.applescript |
— — AppDelegate.applescript — process Control test — — Created by Takaaki Naganoya2 on 2024/11/01. — — script AppDelegate property parent : class "NSObject" — IBOutlets property theWindow : missing value property NSMenu : a reference to current application’s NSMenu property NSMenuItem : a reference to current application’s NSMenuItem property NSStatusBar : a reference to current application’s NSStatusBar property NSVariableStatusItemLength : a reference to current application’s NSVariableStatusItemLength on applicationWillFinishLaunching:aNotification my initMenu:me end applicationWillFinishLaunching: on applicationShouldTerminate:sender return current application’s NSTerminateNow end applicationShouldTerminate: on clicked:aSender my activateMe:(me) set aTag to (tag of aSender) as integer –Important!! set aTitle to (title of aSender) as string if aTag is in {41, 42, 43, 44, 45, 46} then my activateMe:aSender else if aTag = 100 then display dialog "Clicked" end if if aTitle = "Quit" then quit end clicked: on activateMe:aSender current application’s NSApp’s activateIgnoringOtherApps:(true) theWindow’s makeKeyAndOrderFront:(me) theWindow’s orderFrontRegardless() end activateMe: –Status Menu Structure on initMenu:aSender set aList to {"Piyomaru", "Software", "", "Function", {"1", "2", "3", "4", "5"}, "", "Quit"} set aStatusItem to current application’s NSStatusBar’s systemStatusBar()’s statusItemWithLength:(current application’s NSVariableStatusItemLength) aStatusItem’s setTitle:" TEST " aStatusItem’s setHighlightMode:true aStatusItem’s setMenu:(createMenu(aList) of me) end initMenu: on createMenu(aList) set aMenu to current application’s NSMenu’s alloc()’s init() set aCount to 10 set prevMenuItem to "" repeat with i in aList set j to contents of i set aClass to (class of j) as string if j is equal to "" then set aMenuItem to (current application’s NSMenuItem’s separatorItem()) (aMenu’s addItem:aMenuItem) else if (aClass = "text") or (aClass = "string") then if j = "Quit" then set aMenuItem to (current application’s NSMenuItem’s alloc()’s initWithTitle:j action:"clicked:" keyEquivalent:"") else set aMenuItem to (current application’s NSMenuItem’s alloc()’s initWithTitle:j action:"clicked:" keyEquivalent:"") end if (aMenuItem’s setTag:aCount) (aMenuItem’s setTarget:me) (aMenu’s addItem:aMenuItem) set aCount to aCount + 10 copy aMenuItem to prevMenuItem else if aClass = "list" then –Generate Submenu set subMenu to current application’s NSMenu’s new() (aMenuItem’s setSubmenu:subMenu) set subCounter to 1 repeat with ii in j set jj to contents of ii set subMenuItem1 to (current application’s NSMenuItem’s alloc()’s initWithTitle:jj action:"clicked:" keyEquivalent:"") (subMenuItem1’s setTarget:me) (subMenuItem1’s setTag:(aCount + subCounter)) (subMenu’s addItem:subMenuItem1) set subCounter to subCounter + 1 end repeat end if end if end repeat return aMenu end createMenu end script |
使用中のmacOS環境にインストールされているフォントのmostCompatibleStringEncodingを求めて、集計出力するAppleScriptです。
mostCompatibleStringEncodingはフォントが対応しているエンコーディングということで、普通そんなものはないように思えますが、EnglishなどのAscii & Numelicだけの言語用フォントであれば、「それにしか対応していない」という意味でのmostCompatibleStringEncodingはあるんじゃないかと。
一応調査するために書いてみたものです。バーコード系のフォントやドットフォントが該当しそうな感じです。
AppleScript名:全フォントのmostCompatibleStringEncodingを求める.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/11/06 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript use scripting additions use framework "Foundation" property NSFont : a reference to current application’s NSFont property NSPredicate : a reference to current application’s NSPredicate property NSFontManager : a reference to current application’s NSFontManager script spdf property fList : {} property outList : {} end script set (outList of spdf) to {} set (fList of spdf) to getEveryFontPSName() of me repeat with i in (fList of spdf) set j to contents of i set aEnc to chkFontsCompatibleEncoding(j) of me –if aEnc is not equal to "default" then set the end of (outList of spdf) to {fontEncoding:aEnc} set the end of (outList of spdf) to {fontEncoding:aEnc} end repeat set aCountedList to countEachRecord((outList of spdf)) of me –> {{aCount:1, aData:{fontEncoding:"NSSymbolStringEncoding"}}, {aCount:860, aData:{fontEncoding:"default"}}, {aCount:1005, aData:{fontEncoding:"NSMacOSRomanStringEncoding"}}} –Rec in Listの登場頻度を集計して出力 on countEachRecord(aRecList) set theCountedSet to current application’s NSCountedSet’s |set|() repeat with i in aRecList set j to contents of i (theCountedSet’s addObject:j) end repeat set theEnumerator to theCountedSet’s objectEnumerator() set anArray to current application’s NSMutableArray’s alloc()’s init() repeat set aDict to current application’s NSMutableDictionary’s alloc()’s init() set aValue to theEnumerator’s nextObject() if aValue is missing value then exit repeat set aCount to theCountedSet’s countForObject:aValue aDict’s setObject:aCount forKey:"aCount" aDict’s setObject:aValue forKey:"aData" anArray’s addObject:aDict end repeat return anArray as anything end countEachRecord on getEveryFontPSName() script spd property aList : {} end script set aFontList to NSFontManager’s sharedFontManager()’s availableFonts() set thePred to NSPredicate’s predicateWithFormat:"NOT SELF BEGINSWITH ’.’" set aFontList to (aFontList’s filteredArrayUsingPredicate:thePred) as list set aList of spd to {} repeat with i in aFontList set aName to contents of i set the end of aList of spd to aName end repeat return aList of spd end getEveryFontPSName on chkFontsCompatibleEncoding(fontPSName as string) set aFont to current application’s NSFont’s fontWithName:(fontPSName) |size|:16 if aFont’s mostCompatibleStringEncoding() = (current application’s NSASCIIStringEncoding) then return "NSASCIIStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSNEXTSTEPStringEncoding) then return "NSNEXTSTEPStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSJapaneseEUCStringEncoding) then return "NSJapaneseEUCStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSUTF8StringEncoding) then return "NSUTF8StringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSISOLatin1StringEncoding) then return "NSISOLatin1StringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSSymbolStringEncoding) then return "NSSymbolStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSNonLossyASCIIStringEncoding) then return "NSNonLossyASCIIStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSShiftJISStringEncoding) then return "NSShiftJISStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSISOLatin2StringEncoding) then return "NSISOLatin2StringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSUnicodeStringEncoding) then return "NSUnicodeStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSWindowsCP1251StringEncoding) then return "NSWindowsCP1251StringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSWindowsCP1252StringEncoding) then return "NSWindowsCP1252StringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSWindowsCP1253StringEncoding) then return "NSWindowsCP1253StringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSWindowsCP1254StringEncoding) then return "NSWindowsCP1254StringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSWindowsCP1250StringEncoding) then return "NSWindowsCP1250StringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSISO2022JPStringEncoding) then return "NSISO2022JPStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSMacOSRomanStringEncoding) then return "NSMacOSRomanStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSProprietaryStringEncoding) then return "NSProprietaryStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSISO2022JPStringEncoding) then return "NSISO2022JPStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSJapaneseEUCStringEncoding) then return "NSJapaneseEUCStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSShiftJISStringEncoding) then return "NSShiftJISStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSUTF16BigEndianStringEncoding) then return "NSUTF16BigEndianStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSUTF16LittleEndianStringEncoding) then return "NSUTF16LittleEndianStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSUTF16StringEncoding) then return "NSUTF16StringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSUnicodeStringEncoding) then return "NSUnicodeStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSUTF32BigEndianStringEncoding) then return "NSUTF32BigEndianStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSUTF32LittleEndianStringEncoding) then return "NSUTF32LittleEndianStringEncoding" else if aFont’s mostCompatibleStringEncoding() = (current application’s NSUTF32StringEncoding) then return "NSUTF32StringEncoding" else return "default" end if end chkFontsCompatibleEncoding |
PostScript名で指定したフォントにTraitMaskを付与し、そのPostScript名の文字列を返すAppleScriptです。
指定フォントのBoldバージョンを取得する、という用途においてはこれで済むのですが、すでにBold指定されたフォントを渡したときにExtra Boldのフォントを返すという処理には使えません。
AppleScript名:PostScript名で指定のフォントのTraitMaskを付与する_v3.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/10/19 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript use framework "Foundation" use framework "AppKit" use scripting additions property NSFont : a reference to current application’s NSFont property NSFontManager : a reference to current application’s NSFontManager set aName to "TimesNewRomanPSMT" set traitsNum to (current application’s NSBoldFontMask) set bName to getTraitedFontName(aName, traitsNum) of me –> "TimesNewRomanPS-BoldMT" on getTraitedFontName(aName, traitsNum) set bFont to getBoldFontObj(aName, traitsNum) of me return bFont’s fontName() as string end getTraitedFontName –BoldフォントのNSFontを取得 on getBoldFontObj(aName, traitsNum) set aFont to current application’s NSFont’s fontWithName:aName |size|:9.0 –> (NSCTFont) "HelveticaNeue 9.00 pt. P [] (0x4864eb0e0) fobj=0x12ee73e90, spc=2.50" set fontM to current application’s NSFontManager’s sharedFontManager() –指定のフォントがBoldのtraitを持っているかをチェック set fRes to fontM’s fontNamed:aName hasTraits:traitsNum if (fRes as boolean) = true then return aFont –すでに該当する set bFont to fontM’s convertFont:(aFont) toHaveTrait:(traitsNum) return bFont end getBoldFontObj –指定フォントのファミリーに属するフォント数を取得 on countFontsInItsFamily(aPSName) set aFont to current application’s NSFont’s fontWithName:(aPSName) |size|:9.0 set aFamily to aFont’s familyName() set fMan to current application’s NSFontManager’s sharedFontManager() set fList to fMan’s availableMembersOfFontFamily:aFamily return length of (fList as list) end countFontsInItsFamily |
macOSにインストールされているすべてのフォントに対して、すべてのtraitsを確認して、タブ区切りテキストとして出力するAppleScriptです。
実際にNumbersの表データに入力して、Numbers上のフィルタを用いてtraitsの分布状況を調べてみました。とくに問題はないようです。
AppleScript名:全フォントから全traitsを調べる v2.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/11/03 — – Copyright © 2024 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 NSFont : a reference to current application’s NSFont property NSPredicate : a reference to current application’s NSPredicate property NSFontManager : a reference to current application’s NSFontManager set fontStatList to {} set fList to getEveryFontPSName() of me set fontM to NSFontManager’s sharedFontManager() repeat with i in fList set j to contents of i set f1Res to (fontM’s fontNamed:(j) hasTraits:(current application’s NSItalicFontMask)) as integer set f2Res to (fontM’s fontNamed:(j) hasTraits:(current application’s NSBoldFontMask)) as integer set f3Res to (fontM’s fontNamed:(j) hasTraits:(current application’s NSUnboldFontMask)) as integer set f4Res to (fontM’s fontNamed:(j) hasTraits:(current application’s NSNonStandardCharacterSetFontMask)) as integer set f5Res to (fontM’s fontNamed:(j) hasTraits:(current application’s NSNarrowFontMask)) as integer set f6Res to (fontM’s fontNamed:(j) hasTraits:(current application’s NSExpandedFontMask)) as integer set f7Res to (fontM’s fontNamed:(j) hasTraits:(current application’s NSCondensedFontMask)) as integer set f8Res to (fontM’s fontNamed:(j) hasTraits:(current application’s NSSmallCapsFontMask)) as integer set f9Res to (fontM’s fontNamed:(j) hasTraits:(current application’s NSPosterFontMask)) as integer set fARes to (fontM’s fontNamed:(j) hasTraits:(current application’s NSCompressedFontMask)) as integer set fBRes to (fontM’s fontNamed:(j) hasTraits:(current application’s NSFixedPitchFontMask)) as integer set fCRes to (fontM’s fontNamed:(j) hasTraits:(current application’s NSUnitalicFontMask)) as integer set the end of fontStatList to {j, f1Res, f2Res, f3Res, f4Res, f5Res, f6Res, f7Res, f8Res, f9Res, fARes, fBRes, fCRes} end repeat –結果のリストをタブ区切りテキストに set sRes to make2DList2TabSepMultilineText(fontStatList) of me –> (* 07LightNovelPOP 0 1 0 0 0 0 0 0 0 0 0 0 07YasashisaGothic 0 0 0 0 0 0 0 0 0 0 0 0 07YasashisaGothicBold 0 0 0 0 0 0 0 0 0 0 0 0 7barP 0 0 0 0 0 0 0 0 0 0 0 0 7barSP 0 0 0 0 0 0 0 0 0 0 0 0…….. *) on getEveryFontPSName() set aFontList to NSFontManager’s sharedFontManager()’s availableFonts() set thePred to NSPredicate’s predicateWithFormat:"NOT SELF BEGINSWITH ’.’" set aFontList to (aFontList’s filteredArrayUsingPredicate:thePred) as list set aList to {} repeat with i in aFontList set aName to contents of i set the end of aList to aName end repeat return aList end getEveryFontPSName on make2DList2TabSepMultilineText(aList) set aStr to "" repeat with i in aList set j to contents of i set tmpStr to list2TABseparatedText(j) of me set aStr to aStr & tmpStr & return end repeat return aStr end make2DList2TabSepMultilineText –リストをタブ区切りのテキストに変換 on list2TABseparatedText(aList) set curDelim to AppleScript’s text item delimiters set AppleScript’s text item delimiters to tab set bList to aList as string set AppleScript’s text item delimiters to curDelim return bList end list2TABseparatedText |
欧文フォントを太らせる処理を行う下調べとして、指定PostScriptフォント名のフォントファミリーに、指定のバリエーションが存在するかどうかを調べるAppleScriptを書いてみました。
Classic MacOS時代はフォントを変形させてボールド表現を行ったり、イタリック表現を行うなどの機能が実装されていました。
一方、OPENSTEPを源流に持つmacOSは、さまざまなバリエーションのフォントをあらかじめ用意しておくことが必要です。このあたりの仕様は、なんとなく技術的な後退を感じますが、まあいいでしょう。
そして、NSFontManagerの機能を用いて、指定のフォントバリエーション(ボールド)が存在するかを確認するはずだったのが本AppleScriptです。
ただ、実際に試してみたところ、どのフォントを指定してもダメでした。「ボールド書体はない」と言われます。あれ????
そこで、macOSに入っているすべてのフォントのPostScript名を取得して、すべてのフォントのTraitsをチェック。
結果、けっこうな数のフォントがboldのTraitsを持っている、と返ってきました。名前に「Bold」と入っているフォントばかりが。
つまり、NSFontManagerのfontNamed:① hasTraits:② は、指定のフォント自体にTraitsがあるのか調べるAPIであったということです。自分が誤解していたようでした。
AppleScript名:指定フォントの指定Traitsが存在するかをチェック.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/11/02 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript use framework "Foundation" use framework "AppKit" use scripting additions –set aName to "HelveticaNeue" –set aName to "Verdana" –set aName to "TimesNewRomanPSMT" set aName to "SFProText-Regular" –指定のフォントがBoldのtraitを持っているかをチェック set fontM to current application’s NSFontManager’s sharedFontManager() set fRes to fontM’s fontNamed:aName hasTraits:(current application’s NSBoldFontMask) –> false(みんなfalse) |
AppleScript名:全フォントからtraitsを調べる.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/11/03 — – Copyright © 2024 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 NSFont : a reference to current application’s NSFont property NSPredicate : a reference to current application’s NSPredicate property NSFontManager : a reference to current application’s NSFontManager set okList to {} set fList to getEveryFontPSName() of me set fontM to NSFontManager’s sharedFontManager() repeat with i in fList set j to contents of i set fRes to (fontM’s fontNamed:(j) hasTraits:(current application’s NSBoldFontMask)) if fRes as boolean = true then set the end of okList to j end repeat return okList on getEveryFontPSName() set aFontList to NSFontManager’s sharedFontManager()’s availableFonts() set thePred to NSPredicate’s predicateWithFormat:"NOT SELF BEGINSWITH ’.’" set aFontList to (aFontList’s filteredArrayUsingPredicate:thePred) as list set aList to {} repeat with i in aFontList set aName to contents of i set the end of aList to aName end repeat return aList end getEveryFontPSName |
Keynote書類で選択中のテキストアイテムのうち、各行の冒頭からマークの文字までの間を太文字にするAppleScriptです。
v1を改良し、さまざまな区切り記号に対応させるべく、改修を行なってみたものです。
当初は、各テキストアイテムの内部テキストを解析して、共通記号文字を計算して自動で認識処理を行なってみようかと考えていました。統計処理を行なって共通で登場する文字をピックアップさせることを検討していました。
ただ、これだと複数の選択アイテムで別々の区切り文字を採用している場合に対応できません。
統計処理を行わず、技術的にもっとレベルを下げ、「ゆらぎ」検出のためのオーソドックスな、ゆらぎ表記列挙リストを作って、ひたすらループで処理するように改変。
▲処理後 各テキストアイテムで、指定の記号より前の部分の文字を太くした
なお、本Scriptは書式変更ターゲット文字のピックアップ性能を向上させたものであり、欧文フォントの処理を考慮したものにはなっていません。フォントファミリー内のウェイトを上げたフォントを求めるという処理を行なっています。
fFamilyCount = 2
の場合には、「ヒラギノ角ゴProN W3」を「ヒラギノ角ゴProN W6」に変更する処理を行います。
fFamilyCount > 4
の場合には、「ヒラギノ角ゴシック Wn」のウェイトを上げています。
もしも、利用中のMacにウェイトが多数含まれているフォントをインストールして、Keynote書類上でそのフォントを指定している場合には、ウェイトを上げたフォントを求める処理で、文字を太くするよう処理されることでしょう。
AppleScript名:選択中のtext itemの冒頭のフォントを太くする(フォントのWeightを変更)v2.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/11/01 — – Copyright © 2024 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 NSFont : a reference to current application’s NSFont property NSFontManager : a reference to current application’s NSFontManager –セパレータリスト、表記ゆらぎ対応(ゆらぎ表記個数は可変) property separatorList : {{":", ":"}, {"mm", "㎜"}, {"cm", "cm"}} tell application "Keynote" tell front document set aSel to selection –Keynote上の選択中のオブジェクトでループ repeat with i in aSel set j to contents of i set tmpClass to class of j –選択中のオブジェクトがテキストアイテムの場合に……. if tmpClass = text item then set objText to object text of j set fontName to font of object text of j set fontSize to size of object text of j –フォントを太らせる(ウェイトを上げる) set fFamilyCount to countFontsInItsFamily(fontName) of me if fFamilyCount = 2 then set newFont to incrementFontWeight(fontName, 1) of me else if fFamilyCount > 4 then set newFont to incrementFontWeight(fontName, 4) of me end if set aCount to 1 set tList to splitByLInes(objText) of me –行ごとにParseした行ごとのテキストでループ repeat with ii in tList set jj to contents of ii set anOffset to 0 –セパレータでループ repeat with iii in separatorList –セパレータの「ゆらぎ」表記を考慮してループ repeat with iiii in iii set jjjj to contents of iiii set anOffset to offset of jjjj in jj if anOffset is not equal to 0 then exit repeat end if end repeat if anOffset is not equal to 0 then exit repeat end repeat if anOffset is not equal to 0 then try set font of characters 1 thru (anOffset – 1) of paragraph aCount of object text of j to newFont end try end if set aCount to aCount + 1 end repeat end if end repeat end tell end tell –テキストを行ごとにParse on splitByLInes(someText) — free to a good home set theString to current application’s NSString’s stringWithString:someText set theList to theString’s componentsSeparatedByCharactersInSet:(current application’s NSCharacterSet’s newlineCharacterSet()) return theList as list end splitByLInes –フォントを太らせる。欧文フォントは考慮していない(別の方法で行う) on incrementFontWeight(psFontName, incNum) set aFont to current application’s NSFont’s fontWithName:psFontName |size|:9.0 –> (NSCTFont) "HiraginoSans-W0 9.00 pt. P [] (0x12870af00) fobj=0x11b1e90d0, spc=1.98" set fontM to current application’s NSFontManager’s sharedFontManager() repeat incNum times set aFont to fontM’s convertWeight:true ofFont:aFont end repeat return (aFont’s fontName()) as string end incrementFontWeight –指定フォントのファミリーに属するフォント数を取得 on countFontsInItsFamily(aPSName) set aFont to current application’s NSFont’s fontWithName:(aPSName) |size|:9.0 set aFamily to aFont’s familyName() set fMan to current application’s NSFontManager’s sharedFontManager() set fList to fMan’s availableMembersOfFontFamily:aFamily return length of (fList as list) end countFontsInItsFamily |
macOS 15になって、起動中のアプリ自身が自分のバンドル内のリソースにファイルアクセスできなくなったためか(仮説)、スクリプトエディタで自身のAppleScript用語辞書を確認できていません(macOS 15.1)。
用語辞書を読まずにAppleScriptを書くことはできないので、従来のmacOS 13や14から比べると「困った改変」です。
一応、Script Debuggerを用いてスクリプトエディタのAppleScript用語辞書をブラウズすることは可能であるため、不可能になったわけではないのですが、スクリプトエディタで用語辞書をオープンできないと、辞書内容をファイル保存して、変更点の差分を検出できなくて(私が)困っています。
スクリプトエディタのAppleScript用語辞書は、バンドル内にsdefの形式で格納されなくなったため(.scriptsuites書類?)バンドルの中身を開けてコピーというFinderのような解決策をとることができません。
Keynote書類で選択中のテキストアイテムのうち、各行の冒頭から「:」の文字までの間を太文字にするAppleScriptです。
本Scriptは、処理内容が地味な割に、処理内容(の説明)が大変です。かなり複雑な処理をやっているためです。(フォント名を文字列で組み立てるなどの)もっと乱暴な処理もできるのですが、ここはあえて丁寧な処理を行なってみました。
Keynote書類上の選択中のText item(複数の場合もある)内のObject textにアクセス。ここで、文字情報、フォント情報、フォントサイズ情報、文字色情報などが取得できます。
フォントサイズ情報を取得して(テキストアイテム内はすべて同じフォントが指定されているものと想定)、フォント名がPostScript名で返ってくるので、NSFontManagerの機能を用いて、当該フォントが所属するフォントファミリーを求めます。さらに、そのファミリーにいくつのフォントが所属しているのかを求めます。
ここで想定しているのは、ヒラギノ角ゴ W3/W6かヒラギノ角ゴシックW0〜W9です。欧文フォントでは、ボールド書体がファミリー中に存在するかをチェックし、存在すればボールド書体を指定するといったまったく別の処理が必要です。本Scriptはとりあえずやりたいことを詰め込んで動くレベルにまとめただけで、汎用性はあまりありません。
また、Keynoteのtext item中の「行」(paragraph)へのアクセスが安定していません。改行をリターンキーだけで行うか、Shift-Returnで行うか、Control-Returnで行うかといった些細な操作の違いによって行カウントできる行数に差が発生します。
安全のためには、AppleScript上でRTF(NSMutableAttributedString)を作って、そこでフォントの変更を行なってtext itemのobject textに書き戻すのが理想的です。
AppleScript名:選択中のtext itemの冒頭のフォントを太くする.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/10/19 — – Copyright © 2024 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 NSFont : a reference to current application’s NSFont property NSFontManager : a reference to current application’s NSFontManager tell application "Keynote" tell front document set aSel to selection repeat with i in aSel set j to contents of i set tmpClass to class of j if tmpClass = text item then set objText to object text of j set fontName to font of object text of j set fontSize to size of object text of j –フォントを太らせる(ウェイトを上げる) set fFamilyCount to countFontsInItsFamily(fontName) of me if fFamilyCount = 2 then set newFont to incrementFontWeight(fontName, 1) of me else if fFamilyCount > 4 then set newFont to incrementFontWeight(fontName, 4) of me end if set aCount to 1 set tList to splitByLInes(objText) of me repeat with ii in tList set jj to contents of ii set anOffset1 to offset of ":" in jj set anOffset2 to offset of ":" in jj if {anOffset1, anOffset2} is not equal to {0, 0} then if anOffset1 = 0 then set offRes to anOffset2 else if anOffset2 = 0 then set offRes to anOffset1 else set offRes to anOffset1 end if try set font of characters 1 thru offRes of paragraph aCount of object text of j to newFont end try set aCount to aCount + 1 end if end repeat end if end repeat end tell end tell –テキストを行ごとにParse on splitByLInes(someText) — free to a good home set theString to current application’s NSString’s stringWithString:someText set theList to theString’s componentsSeparatedByCharactersInSet:(current application’s NSCharacterSet’s newlineCharacterSet()) return theList as list end splitByLInes –フォントを太らせる。欧文フォントは考慮していない(別の方法で行う) on incrementFontWeight(psFontName, incNum) set aFont to current application’s NSFont’s fontWithName:psFontName |size|:9.0 –> (NSCTFont) "HiraginoSans-W0 9.00 pt. P [] (0x12870af00) fobj=0x11b1e90d0, spc=1.98" set fontM to current application’s NSFontManager’s sharedFontManager() repeat incNum times set aFont to fontM’s convertWeight:true ofFont:aFont end repeat return (aFont’s fontName()) as string end incrementFontWeight –指定フォントのファミリーに属するフォント数を取得 on countFontsInItsFamily(aPSName) set aFont to current application’s NSFont’s fontWithName:(aPSName) |size|:9.0 set aFamily to aFont’s familyName() set fMan to current application’s NSFontManager’s sharedFontManager() set fList to fMan’s availableMembersOfFontFamily:aFamily return length of (fList as list) end countFontsInItsFamily |
NSFontManagerを用いて、指定フォントのウェイト(太さ)を上げたり下げたりする処理ができますが、実際にやってみると「見えなかったもの」が見えてきます。
macOSには「ヒラギノ角ゴシック」のW0(PostScript名:HiraginoSans-W0)からW9(PostScript名:HiraginoSans-W9)まで異なるウェイトのフォントがインストールされています。そこで、W0から順次W9までウェイトを上げる処理を行なってみると、「ヒラギノ角ゴシックW2」にウェイトが割り振られていないことがわかります。
バグなのか、意図的なものなのか……多分バグだと思うのですが……。
AppleScript名:PostScript名で指定のフォントのウェイトを上げるループ.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/10/19 — – Copyright © 2024 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 NSFont : a reference to current application’s NSFont property NSFontManager : a reference to current application’s NSFontManager set aName to "HiraginoSans-W0" set aFont to current application’s NSFont’s fontWithName:aName |size|:9.0 –> (NSCTFont) "HiraginoSans-W0 9.00 pt. P [] (0x12870af00) fobj=0x11b1e90d0, spc=1.98" set fontM to current application’s NSFontManager’s sharedFontManager() set fRes to fontM’s weightOfFont:(aFont) –> 2 repeat 12 times set fRes to fontM’s weightOfFont:(aFont) log (fRes as number) set aFontName to aFont’s fontName() log aFontName (*2*) (*(NSString) "HiraginoSans-W0"*) (*3*) (*(NSString) "HiraginoSans-W1"*) — <– W2は????? (*4*) (*(NSString) "HiraginoSans-W3"*) (*5*) (*(NSString) "HiraginoSans-W4"*) (*6*) (*(NSString) "HiraginoSans-W5"*) (*8*) (*(NSString) "HiraginoSans-W6"*) (*9*) (*(NSString) "HiraginoSans-W7"*) (*10*) (*(NSString) "HiraginoSans-W8"*) (*12*) (*(NSString) "HiraginoSans-W9"*) set aFont to fontM’s convertWeight:true ofFont:aFont end repeat |
ヒラギノ角ゴシックW2のウェイトを取得したら3が返ってきました。ウェイトがW2とW3で重なっているんですね。
AppleScript名:PostScript名で指定のフォントのウェイトを取得.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/10/19 — – Copyright © 2024 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 NSFont : a reference to current application’s NSFont property NSFontManager : a reference to current application’s NSFontManager set aName to "HiraginoSans-W2" set aFont to current application’s NSFont’s fontWithName:aName |size|:9.0 –> (NSCTFont) "HiraginoSans-W0 9.00 pt. P [] (0x12870af00) fobj=0x11b1e90d0, spc=1.98" set fontM to current application’s NSFontManager’s sharedFontManager() set fRes to fontM’s weightOfFont:(aFont) –> 3 |
Betaのときには起こっていなかった現象で、macOS 15.1で確認されています。
スクリプトエディタでFinderのAppleScript用語辞書を閲覧できません。
Finderは/System/Library/CoreServicesフォルダに入っており、このフォルダへのアクセスが「より厳しく」なったのでしょう。
ただ、実際にFinderがAppleScript対応しなくなったわけでもなければ、FinderのSDEFが除去されたわけでもありません。
実際にFinderの(パッケージ内容をコンテクストメニューから表示させたうえで)SDEFをコピーして、コピーしたものをスクリプトエディタで閲覧するとよいでしょう。
macOS 13.7.1が動作中のMac mini M1から、LAN経由でmacOS 15.1が動いているM2 MacBook AirのFinderにRemote Apple Eventsで操作を行なってみたら、M2 Air(macOS 15.1)側のFinderがクラッシュするという症状に直面しています。
同じ操作をM2 Air側からM1 Mac miniに対して実行すると、問題なく結果が返ってきます。
バグ?
▲M1 Mac mini上で書いたAppleScript。コンパイル(構文確認)を行おうとすると、ユーザー認証までは進むものの、直後にmacOS 15環境側のFinderがクラッシュする
アプリからスクリプトエディタを用いて書き出したSDEFファイル(AppleScript用語説明ファイル)から、コマンドを抽出するAppleScriptです。
–> Download sdef command lister.scptd (Including AS Lib in its bundle)
個人的に、SDEFの差分を確認するために極力アプリの各バージョンのSDEFを書き出して保存するようにしており、
用語辞書ベースで機能追加や変更が行われたことを確認しています。
こうしたSDEFのままでは比較できないので、FileMerge(XcodeについてくるGUI版diffの傑作)を使ってバージョン間の差分を取って確認するのが「通常営業」なわけです。
これがv1.0から最新版までの経緯をおおまかに把握したい、といったときには細かすぎますし、用途に合っていません。
そこで本Scriptを書いて利用することにしました。各バージョンごとにコマンドを抽出して、存在する/しないといった比較ができれば全体的な傾向を把握できます。
Apple純正アプリのSDEF、Adobe Creative Cloudアプリ、Microsoft OfficeのSDEFなどひととおり処理してみましたが、自分が使った範囲ではとくに問題は見つかっていません。ただ、まだ間違いがあるかもしれないため、処理ミスが見られた場合にはコメントで報告してください。
▲本Scriptを用いて、Skim各バージョンのAppleScript用語辞書からコマンドの分布を表にしたもの
AppleScript名:指定のSDEFファイルからコマンドを抽出.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/10/16 — – Copyright © 2022-2024 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use framework "AppKit" use xmlLib : script "piyoXML" use scripting additions script myDict property sdefDict : {} end script set sdefPath to (POSIX path of (choose file of type {"com.apple.scripting-definition"} with prompt "SDEFファイルを選択")) set comRes to {} set (sdefDict of myDict) to {} set comList to parseSdefAndRetObjects(sdefPath) of me repeat with i in comList set j to contents of i set aRes to parseSdefFileAndRetCommandStructure(sdefPath, j) of me if aRes is not equal to "" then set aRes to "OK" set the end of comRes to j end repeat set aStr to listToStringUsingTextItemDelimiter(comRes, return) of me on parseSdefFileAndRetCommandStructure(targSDEFPath, aTargCom) set suitesList to ((sdefDict of myDict)’s valueForKeyPath:"dictionary.suite.command") repeat with i in suitesList –SuitesでLoop set j to contents of i try if j is not equal to missing value and j is not equal to current application’s NSNull then repeat with ii in j –CommandでLoop set jj to contents of ii set tmpName to jj’s attributes’s |name| if aTargCom is in (tmpName as list) then return jj end repeat end if on error return "" end try end repeat return false end parseSdefFileAndRetCommandStructure –指定Bundle IDのコマンド一覧を取得する on parseSdefAndRetObjects(sdefPath) if (sdefDict of myDict) = {} then set aRes to parseSDEFfileAndRetXMLStr(sdefPath) of me set (sdefDict of myDict) to (xmlLib’s makeRecordWithXML:aRes) end if set e1Res to (sdefDict of myDict)’s valueForKeyPath:"dictionary.suite.command.attributes.name" set fRes to FlattenList(e1Res as list) of me set bList to cleanUp1DList(fRes, {"missing value"}) of me set gRes to uniquify1DList(bList, true) of me return gRes end parseSdefAndRetObjects –SDEFをXincludeを考慮しつつ展開 on parseSDEFfileAndRetXMLStr(sdefFullPath) set sdefAlias to (POSIX file sdefFullPath) as alias –sdefのフルパスを求める –SDEF読み込み(Xincludeの展開が必要な状態) tell current application set theXML to read sdefAlias as «class utf8» end tell –NSXMLDocumentの生成、Xincludeを有効に set {theXMLDoc, theError} to current application’s NSXMLDocument’s alloc()’s initWithXMLString:theXML options:(current application’s NSXMLDocumentXInclude) |error|:(reference) –XMLを文字データ化 set aDocStr to (theXMLDoc’s XMLData) set aDocStr2 to (current application’s NSString’s alloc()’s initWithData:(aDocStr) encoding:(current application’s NSUTF8StringEncoding)) as string return aDocStr2 end parseSDEFfileAndRetXMLStr –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 cleanUp1DList(aList as list, cleanUpItems as list) set bList to {} repeat with i in aList set j to contents of i if j is not in cleanUpItems then set the end of bList to j end if end repeat return bList end cleanUp1DList –1D/2D Listをユニーク化 on uniquify1DList(theList as list, aBool as boolean) set aArray to current application’s NSArray’s arrayWithArray:theList set bArray to aArray’s valueForKeyPath:"@distinctUnionOfObjects.self" if aBool = true then return bArray as list else return bArray end if end uniquify1DList –1D Listを指定デリミタをはさんでテキスト化(Shane Stanley) on listToStringUsingTextItemDelimiter(sourceList, textItemDelimiter) set CocoaArray to current application’s NSArray’s arrayWithArray:sourceList set CocoaString to CocoaArray’s componentsJoinedByString:textItemDelimiter return (CocoaString as string) end listToStringUsingTextItemDelimiter |
Numbersの表を書き換えるためのAppleScriptです。表の選択範囲のうち、空欄でないセルに指定の文字(「●」)を入れます。
電子書籍掲載の「表」を作るのに、割と必要なものです。
「表」をまとめる段階では、
のように、生データをそのままセルに記載しておきますが、場所の利用効率でいえばそのまま生データで掲載していると無駄があります。
そこで、空欄ではないセルについては、「●」などの記号を記入することで、コンパクトな「表」に作り変える作業が発生します。本AppleScriptはそれを自動化したものです。
AppleScript名:選択範囲で空欄でないセルに指定の文字を入れる.scpt |
— – Created by: Takaaki Naganoya – Created on: 2024/10/17 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — script spd property uniqList : {} end script set strMark to "●" tell application "Numbers" tell front document tell active sheet try set theTable to first table whose class of selection range is range on error display notification "Numbers: There is no selection" return end try tell theTable set (uniqList of spd) to cells of selection range repeat with i in (uniqList of spd) set j to contents of i set tmpVal to value of j if tmpVal is not equal to missing value then set value of j to strMark end if end repeat end tell end tell end tell end tell |
指定アプリの各ローカライズ言語における名称(CFBundleName)を取得するAppleScriptです。
まだテスト実装レベルのため、無駄な処理が入っています。
もともと本Scriptは、電子書籍に掲載する表を作成するために書いたものです。
▲電子書籍「AppleScriptによるWebブラウザ自動操縦ガイド」より
こうした資料を掲載する際に、手で調査するのは大変なので、AppleScriptを書いて資料を作成しています。ただ、macOS 13以降で(正確にいえばXcode 15以降で)はローカライズの方法が変更されたため、新たに作られた.loctableデータにアクセスしています。
従来のローカライズ方式と、新方式が混在している状況なので、旧方式でアクセスして値が得られなかった場合には、このScriptを使うとよいのでしょう。
AppleScript名:指定Bundle IDのアプリの各言語のローカライズ名称を取得して出力.scpt |
— – Created by: Takaaki Naganoya – Created on: 2024/10/16 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript version "2.8" use scripting additions use framework "Foundation" property NSArray : a reference to current application’s NSArray property NSPredicate : a reference to current application’s NSPredicate set targAppBundleID to "com.apple.ScriptEditor2" set targKey to "CFBundleName" set aLocale to (current application’s NSLocale’s currentLocale()) set locResList to getAppInfoPlistValueInEveryLocalizedLangs(targAppBundleID, targKey, aLocale) of me –> {{"ヘブライ語", "עורך התסריטים"}, {"韓国語", "스크립트 편집기"}, {"インドネシア語", "Editor Skrip"}, {"オランダ語", "Scripteditor"}, {"トルコ語", "Betik Düzenleyici"}, {"フィンランド語", "Skriptieditori"}, {"ハンガリー語", "Szkriptszerkesztő"}, {"ロシア語", "Редактор скриптов"}, {"イタリア語", "Script Editor"}, {"スペイン語(ラテンアメリカ)", "Editor de Scripts"}, {"ギリシャ語", "Επεξεργασία σκριπτ"}, {"カタロニア語", "Editor de Scripts"}, {"フランス語(カナダ)", "Éditeur de script"}, {"中国語(台湾)", "工序指令編寫程式"}, {"中国語(香港)", "程式碼編寫程式"}, {"ポーランド語", "Edytor skryptów"}, {"スウェーデン語", "Skriptredigerare"}, {"ノルウェー語", "Prosedyreredigering"}, {"アラビア語", "محرر البرامج النصية"}, {"英語", "Script Editor"}, {"デンマーク語", "Instruksværktøj"}, {"ヒンディー語", "स्क्रिप्ट संपादक"}, {"タイ語", "ตัวแก้ไขสคริปต์"}, {"中国語(中国本土)", "脚本编辑器"}, {"英語(イギリス)", "Script Editor"}, {"マレー語", "Editor Skrip"}, {"チェコ語", "Editor skriptů"}, {"スロバキア語", "Script Editor"}, {"英語(オーストラリア)", "Script Editor"}, {"スロベニア語", "Skriptni urejevalnik"}, {"ドイツ語", "Skripteditor"}, {"ベトナム語", "Trình soạn thảo tập lệnh"}, {"ポルトガル語(ブラジル)", "Editor de Scripts"}, {"スペイン語", "Editor de Scripts"}, {"ウクライナ語", "Редактор скриптів"}, {"ルーマニア語", "Editor scripturi"}, {"フランス語", "Éditeur de script"}, {"クロアチア語", "Urednik skripte"}, {"ポルトガル語(ポルトガル)", "Editor de Scripts"}, {"日本語", "スクリプトエディタ"}} on getAppInfoPlistValueInEveryLocalizedLangs(targAppBundleID, targKey, aLocale) script spd property urlList : {} end script –macOS 13以降がターゲット set v1 to system attribute "sys1" –> 10, 11, 12, 13, 14, 15…. if v1 < 13 then error "This Script require macOS 13 or later" –指定アプリのバンドル内のResourceから「InfoPlist.loctable」で終わるファイル名のパスを抽出 tell application "Finder" set defPath to application file id targAppBundleID end tell set defPath to (POSIX path of (defPath as alias)) & "Contents/Resources" –Cocoa流のPOSIX path set fList to getFilesIn(defPath) of me set anArray to NSArray’s arrayWithArray:fList set aPred to NSPredicate’s predicateWithFormat:"SELF ENDSWITH ’InfoPlist.loctable’" set locRes to (anArray’s filteredArrayUsingPredicate:aPred) as list set resList to {} –.loctableファイルでループ(1つだけだが) repeat with i in locRes set j to contents of i set (urlList of spd) to (my readPlistAt:(j)) set langKeys to ((urlList of spd)’s allKeys()) as list –Language Codeでループ repeat with ii in langKeys set jj to contents of ii set aLangDat to ((urlList of spd)’s valueForKey:jj) —plist(=loctable)のlabelでループ set allLangKeys to (aLangDat’s allKeys()) as list repeat with iii in allLangKeys set jjj to contents of iii set aVal to (aLangDat’s valueForKey:(jjj)) if jjj = targKey then set locLangName to getLangNameWithLocale(jj, aLocale) of me set the end of resList to {locLangName, aVal as string} exit repeat end if end repeat end repeat end repeat return resList end getAppInfoPlistValueInEveryLocalizedLangs –Read Plist on readPlistAt:thePath set thePath to current application’s NSString’s stringWithString:thePath set thePath to thePath’s stringByExpandingTildeInPath() set theDict to current application’s NSDictionary’s dictionaryWithContentsOfFile:thePath return theDict end readPlistAt: –指定フォルダ内のファイルのフルパス一覧を返す on getFilesIn(posixPath) script spd property allItems : {} end script set allItems of spd to {} — make URL set theNSURL to current application’s |NSURL|’s fileURLWithPath:posixPath — make file manager set theNSFileManager to current application’s NSFileManager’s new() — get URL enumerator set theNSFileEnumerator to theNSFileManager’s enumeratorAtURL:theNSURL includingPropertiesForKeys:{current application’s NSURLIsDirectoryKey, current application’s NSURLIsPackageKey} options:((current application’s NSDirectoryEnumerationSkipsPackageDescendants) + (current application’s NSDirectoryEnumerationSkipsHiddenFiles as integer)) errorHandler:(missing value) — get all items from enumerator set (allItems of spd) to theNSFileEnumerator’s allObjects() set theFolders to {} — to store folders — loop through repeat with i from 1 to count of (allItems of spd) — is it a directory? set {theResult, isDirectory} to ((item i of (allItems of spd))’s getResourceValue:(reference) forKey:(current application’s NSURLIsDirectoryKey) |error|:(missing value)) if isDirectory as boolean = false then set {theResult, isPackage} to ((item i of (allItems of spd))’s getResourceValue:(reference) forKey:(current application’s NSURLIsPackageKey) |error|:(missing value)) — is it not a package? if not isPackage as boolean then set end of theFolders to (item i of (allItems of spd))’s |path|() as string –«class furl» end if end if end repeat return theFolders end getFilesIn on getLangNameWithLocale(langCode, aLocale) set aLangName to (aLocale’s displayNameForKey:(current application’s NSLocaleIdentifier) value:langCode) as string return aLangName end getLangNameWithLocale |
マウント中のディスクイメージ(.dmg)ファイルのパスを求めるAppleScriptです。
macOS 13.7.1上で作成し、15.1上でも動作確認していますが、それほど込み入った機能は使っていないので、OSバージョン依存はないことでしょう。
実際に、オープンソースのPDFビューワー「Skim」の各バージョンのdmgファイルをダウンロードして、sdef(AppleScript用語説明)ファイルを収集してバージョンごとの変化を追う作業を行なったときに、バージョン判定を行うために元のdmgファイルのパスを求める処理を行なったものです。
▲マウントしたディスクイメージからパス情報を取得することで、一部ファイルを取り出してコピーする際にバージョン情報を反映させた
AppleScript名:マウントしたディスクイメージから元のdmgファイルのパスを取得.scpt |
— – Created by: Takaaki Naganoya – Created on: 2024/10/16 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript use framework "Foundation" use scripting additions set dName to "Skim" –Disk Imageのマウントを確認 tell application "Finder" set dexRes to (get exists of disk dName) end tell if dexRes = false then error "指定のディスクイメージ「" & dName & "」はマウントされていません。" –マウントされていなかった set aRes to getMountedDiskImageInfo("image-path : ") of me –> "/Users/me/Downloads/Skim-1.4.6.dmg" on getMountedDiskImageInfo(targMark) try set aRes to do shell script "hdiutil info" on error return false end try set aResList to paragraphs of aRes repeat with i in aResList set j to contents of i if j begins with targMark then set aOff to offset of targMark in j set aLen to length of targMark set aRes to text (aLen + 1) thru -1 of j return aRes end if end repeat return false end getMountedDiskImageInfo |
Numbers上でデータを整理しているときに、必要になって組んでみたものです。少し大きいAppleScriptですが、ありもののルーチンをつなぎ合わせて書いたので、とくに画期的な何かを書いたということはありません。
もともと、Numbers上にまとめたデータ(2列分)があって、それらを同じものは同じ行に並べ、削除されたものは空欄、追加されたものは下に列挙するという整理を行なっていました。
▲処理前の状態。2列x処理を行う分の行を選択した状態で本AppleScriptを実行する(スクリプトメニューなどを推奨)
この作業をまとめて行うものです。そんなに高速化に配慮しているわけではないので、巨大なデータを扱うのには(このままでは)向いていないかもしれません。ただ、高速化の余地はいろいろあります。
もう、これは分かりきっていた話ですが、Numbers向けに凝ったAppleScriptを書くと、Numbersの機能はほとんど使わずに、AppleScriptの配列やらNSObjectのNSArrayやらを使いまくった「Cocoaの機能のオンパレード」みたいなAppleScriptになります。これは、Numbersに絶望的に何も機能がないためで、データの入出力しか行う余地がありません。
AppleScript名:選択中の2列のセルを比較して並べ直して書き戻す v2 |
— – Created by: Takaaki Naganoya – Created on: 2024/10/14 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" use scripting additions use framework "Foundation" set curTable to getCurTable() of me if curTable = "" then return set {s1Row, s1Column, e1Row, e1Column} to getSelectionRange(curTable) of me –> {73, 3, 86, 4} set colWidth to e1Column – s1Column + 1 –> 2 if colWidth is not equal to 2 then display dialog "Numbers上の表の選択幅が2ではないので、処理終了します" buttons {"OK"} default button 1 return end if set rowHeight to e1Row – s1Row + 1 –> 15 –縦方向に列のセルの値を取得(左側) set v1List to getValueByRange(curTable, s1Column, s1Row, e1Row) of me –スイープ(空白セルのmissing valueを除去) set v1aList to sweepList(v1List, missing value) of me –> {"com.apple.eloquence.fr-CA.Grandpa", "com.apple.eloquence.fi-FI.Grandpa", " com.apple.eloquence.de-DE.Grandpa", " com.apple.eloquence.pt-BR.Grandpa", "com.apple.eloquence.en-US.Grandpa", "com.apple.eloquence.es-ES.Grandpa", " com.apple.eloquence.en-GB.Grandpa", " com.apple.eloquence.it-IT.Grandpa", " com.apple.eloquence.es-MX.Grandpa"} –縦方向に列のセルの値を取得(右側) set v2List to getValueByRange(curTable, e1Column, s1Row, e1Row) of me –スイープ(空白セルのmissing valueを除去) set v2aList to sweepList(v2List, missing value) of me –> {"com.apple.eloquence.de-DE.Grandpa", "com.apple.eloquence.en-GB.Grandpa", "com.apple.eloquence.en-US.Grandpa", "com.apple.eloquence.es-ES.Grandpa", "com.apple.eloquence.es-MX.Grandpa", "com.apple.eloquence.fi-FI.Grandpa", "com.apple.eloquence.fr-CA.Grandpa", "com.apple.eloquence.fr-FR.Grandpa", "com.apple.eloquence.it-IT.Grandpa", "com.apple.eloquence.pt-BR.Grandpa", "com.apple.eloquence.ja-JP.Grandpa", "com.apple.eloquence.ko-KR.Grandpa", "com.apple.eloquence.zh-CN.Grandpa", "com.apple.eloquence.zh-TW.Grandpa"} set dList to getDiffBetweenLists(v1aList, v2aList) of me –> {addItems:{"com.apple.eloquence.zh-TW.Grandpa", "com.apple.eloquence.ko-KR.Grandpa", "com.apple.eloquence.ja-JP.Grandpa", "com.apple.eloquence.zh-CN.Grandpa"}, minusItems:{}} set newList to {} set minusList to minusItems of dList repeat with i in v1aList set j to contents of i if j is not in minusList then if j is in v2aList then set the end of newList to j else set the end of newList to "" end if else set the end of newList to "" end if end repeat set addList to addItems of dList set addList to sort1DNumList(addList, true) of me –昇順ソート set new2List to newList & addList set newLen to length of new2List if newLen > rowHeight then display dialog "新規データが大きいため(" & (rowHeight as string) & "行に対して、データが" & (newLen as string) & "件)データをNumbersに描き戻せません" return –元のデータ範囲よりも、新規データ範囲(行)が多かったら処理を打ち切る(将来アップデート予定。行挿入などで対処) end if repeat (rowHeight – newLen + 1) times set the end of new2List to "" end repeat setValueByRange(curTable, e1Column, s1Row, e1Row, new2List) of me on setValueByRange(curTable, s1Column, s1Row, e1Row, vList) tell application "Numbers" tell curTable tell column s1Column set cList to cells s1Row thru e1Row end tell set aCount to 1 repeat with i in cList set aVal to item aCount of vList set value of i to aVal set aCount to aCount + 1 end repeat end tell end tell end setValueByRange on getValueByRange(curTable, s1Column, s1Row, e1Row) tell application "Numbers" tell curTable tell column s1Column set vList to value of cells s1Row thru e1Row return vList end tell end tell end tell end getValueByRange on getCurTable() 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 "" –何も選択されてなかった場合 end try return theTable end tell end tell end tell end getCurTable on getSelectionRange(theTable) tell application "Numbers" tell theTable set aSel to properties of selection range set selName to name of aSel set {s1, s2} to parseByDelim(selName, ":") of me –始点の情報を取得する set s1Row to (address of row of range s1) as integer set s1Column to (address of column of range s1) as integer –終点の情報を取得する set e1Row to (address of row of range s2) as integer set e1Column to (address of column of range s2) as integer return {s1Row, s1Column, e1Row, e1Column} end tell end tell end getSelectionRange on parseByDelim(aData, aDelim) set curDelim to AppleScript’s text item delimiters set AppleScript’s text item delimiters to aDelim set dList to text items of aData set AppleScript’s text item delimiters to curDelim return dList end parseByDelim on sweepList(aList, sweepItem) set bList to {} repeat with i in aList if contents of i is not equal to sweepItem then set the end of bList to contents of i end if end repeat return bList end sweepList –リストの差分を取得_v2 on getDiffBetweenLists(aArray as list, bArray as list) set allSet to current application’s NSMutableSet’s setWithArray:aArray allSet’s addObjectsFromArray:bArray –重複する要素のみ抜き出す set duplicateSet to current application’s NSMutableSet’s setWithArray:aArray duplicateSet’s intersectSet:(current application’s NSSet’s setWithArray:bArray) –重複部分を削除する allSet’s minusSet:duplicateSet set resArray to (allSet’s allObjects()) as list set aSet to current application’s NSMutableSet’s setWithArray:aArray set bSet to current application’s NSMutableSet’s setWithArray:resArray aSet’s intersectSet:bSet –積集合 set addRes to aSet’s allObjects() as list set cSet to current application’s NSMutableSet’s setWithArray:bArray cSet’s intersectSet:bSet –積集合 set minusRes to cSet’s allObjects() as list return {addItems:minusRes, minusItems:addRes} end getDiffBetweenLists –1D List(数値)をsort / ascOrderがtrueだと昇順ソート、falseだと降順ソート on sort1DNumList(theList as list, aBool as boolean) tell current application’s NSSet to set theSet to setWithArray_(theList) tell current application’s NSSortDescriptor to set theDescriptor to sortDescriptorWithKey_ascending_(missing value, aBool) set sortedList to theSet’s sortedArrayUsingDescriptors:{theDescriptor} return (sortedList) as list end sort1DNumList |
コピペで作ったNumbersの表に空白文字(ゴミ)が入っている場合のクリーニング用AppleScriptです。Numbers v14.2用に作成しましたが、バージョンが異なっても動作に変化はないでしょう。
ExcelやNumbersなどの表計算アプリでは、前のセルに入力されたフォーマットを維持する機能がついているため、前のセルが空白文字ではじまる(=ゴミが入っている)と、次のセルにも同様にゴミが入ることになります。
そこで、クリーニング用に本Scriptを作成しました。
▲こんな風に、セルの先頭にスペースが入ってしまっている場合のクリーニング用Script。対象セルを選択して本Scriptを実行
▲本Script実行後の様子。各セルの冒頭に入っていたスペースが削除される
ただ、大量のセルを処理するような配慮は一切行っていないので、広範囲のセルを処理する場合には、もっと真剣に高速化を行う必要があることでしょう。
macOS標準装備のスクリプトメニューに入れて呼び出すことを想定しています。
AppleScript名:選択範囲のセルの前後の空白を削除して書き戻す |
— – Created by: Takaaki Naganoya – Created on: 2024/10/14 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" use scripting additions use framework "Foundation" property NSString : a reference to current application’s NSString property NSCharacterSet : a reference to current application’s NSCharacterSet set curTable to getCurTable() of me if curTable = "" then return tell application "Numbers" tell curTable set cList to every cell of selection range repeat with i in cList set j to contents of i set tmpVal to value of j if tmpVal is not equal to missing value then set tmpVal to trimWhiteSpaceFromHeadAndTail(tmpVal) of me ignoring application responses set value of j to tmpVal end ignoring end if end repeat end tell end tell on getCurTable() 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 "" –何も選択されてなかった場合 end try return theTable end tell end tell end tell end getCurTable on trimWhiteSpaceFromHeadAndTail(aStr as string) set aString to NSString’s stringWithString:aStr set bString to aString’s stringByTrimmingCharactersInSet:(NSCharacterSet’s whitespaceAndNewlineCharacterSet) return bString as anything –as anything end trimWhiteSpaceFromHeadAndTail |
macOS上のTTS Voiceから高音質のものだけを抽出するAppleScriptです。
macOS上のTTS環境が変化しており、「VoiceIdentifierが『premium』で終了しているもの」といったルールでは抽出できなくなりました。
そこで、実際にTTS Voiceを調査してmacOS 15時代の実態に合っている抽出を行なってみました(コロコロ変わるので次のOSで変わっているかも?)。
IDに「premium」が含まれるもののほか、「enhanced」が含まれているものが高音質音声であるもようです。
なお、実行結果はTTS Voiceのインストール状況に応じて変わる可能性が高く、実行環境ごとに異なるものとなるはずです。
AppleScript名:EnhancedやPremiumなどの高音質音声を取得.scptd |
— Created 2018-02-15 by Takaaki Naganoya — Modified 2024-10-13 by Takaaki Naganoya — 2018-2024 Piyomaru Software use AppleScript version "2.8" use scripting additions use framework "Foundation" property NSColor : a reference to current application’s NSColor property NSArray : a reference to current application’s NSArray property NSSortDescriptor : a reference to current application’s NSSortDescriptor set vList to getTTSPremiumVoiceName() of me –> {"Allison(拡張)", "Ava(拡張)", "Chantal(拡張)", "Daniel(拡張)", "Evan(拡張)", "Joelle(拡張)", "Kate(拡張)", "Kyoko(拡張)", "Moira(拡張)", "Nathan(拡張)", "Noelle(拡張)", "Otoya(拡張)", "Samantha(拡張)", "Susan(拡張)", "Tom(拡張)", "Zoe(拡張)", "Amélie(プレミアム)", "Ava(プレミアム)", "Jamie(プレミアム)", "Zoe(プレミアム)"} set vIDList to getTTSPremiumVoiceID() of me –> {"com.apple.voice.enhanced.en-US.Allison", "com.apple.voice.enhanced.en-US.Ava", "com.apple.voice.enhanced.fr-CA.Chantal", "com.apple.voice.enhanced.en-GB.Daniel", "com.apple.voice.enhanced.en-US.Evan", "com.apple.voice.enhanced.en-US.Joelle", "com.apple.voice.enhanced.en-GB.Kate", "com.apple.voice.enhanced.ja-JP.Kyoko", "com.apple.voice.enhanced.en-IE.Moira", "com.apple.voice.enhanced.en-US.Nathan", "com.apple.voice.enhanced.en-US.Noelle", "com.apple.voice.enhanced.ja-JP.Otoya", "com.apple.voice.enhanced.en-US.Samantha", "com.apple.voice.enhanced.en-US.Susan", "com.apple.voice.enhanced.en-US.Tom", "com.apple.voice.enhanced.en-US.Zoe", "com.apple.voice.premium.fr-CA.Amelie", "com.apple.voice.premium.en-US.Ava", "com.apple.voice.premium.en-GB.Malcolm", "com.apple.voice.premium.en-US.Zoe"} on getTTSPremiumVoiceName() set outArray to current application’s NSMutableArray’s new() –Make Installed Voice List set aList to current application’s NSSpeechSynthesizer’s availableVoices() set bList to aList as list repeat with i in bList set j to contents of i set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j) (outArray’s addObject:aDIc) end repeat set aPredicate to current application’s NSPredicate’s predicateWithFormat_("VoiceIdentifier contains[cd] %@ ", "enhanced") set afilteredArray to outArray’s filteredArrayUsingPredicate:aPredicate set aResList to (afilteredArray’s valueForKey:"VoiceName") as list set bPredicate to current application’s NSPredicate’s predicateWithFormat_("VoiceIdentifier contains[cd] %@ ", "premium") set afilteredArray to outArray’s filteredArrayUsingPredicate:bPredicate set bResList to (afilteredArray’s valueForKey:"VoiceName") as list return (aResList & bResList) end getTTSPremiumVoiceName on getTTSPremiumVoiceID() set outArray to current application’s NSMutableArray’s new() –Make Installed Voice List set aList to current application’s NSSpeechSynthesizer’s availableVoices() set bList to aList as list repeat with i in bList set j to contents of i set aDIc to (current application’s NSSpeechSynthesizer’s attributesForVoice:j) (outArray’s addObject:aDIc) end repeat set aPredicate to current application’s NSPredicate’s predicateWithFormat_("VoiceIdentifier contains[cd] %@ ", "enhanced") set afilteredArray to outArray’s filteredArrayUsingPredicate:aPredicate set aResList to (afilteredArray’s valueForKey:"VoiceIdentifier") as list set bPredicate to current application’s NSPredicate’s predicateWithFormat_("VoiceIdentifier contains[cd] %@ ", "premium") set afilteredArray to outArray’s filteredArrayUsingPredicate:bPredicate set bResList to (afilteredArray’s valueForKey:"VoiceIdentifier") as list return (aResList & bResList) end getTTSPremiumVoiceID |