Archive for the '正規表現(regex)' Category

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

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

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

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

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

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

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

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

AppleScript名:1D Listから条件抽出(正規表現1)
– Created 2017-11-03 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4949

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

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

set anArray to NSArray’s arrayWithArray:aList

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

★Click Here to Open This Script 

AppleScript名:1D Listから条件抽出(正規表現2)
– Created 2017-11-03 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4949

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

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

set anArray to NSArray’s arrayWithArray:aList

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

★Click Here to Open This Script 

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

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

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

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

tui4.png

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

—————

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

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

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

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

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

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

★Click Here to Open This Script 

2017/09/05 指定フォルダ以下のテキストファイルのファイル名冒頭についている数字から欠番を求める

ファイル名の先頭に1〜3桁の数字がついているテキストファイルに対して、指定フォルダ以下のものをすべて取得し、これらの数(連番)に抜けがないかをチェックするAppleScriptです。

filenames2.png

指定フォルダ以下に存在するテキストファイルをSpotlightで検索し、すべてのファイル名を取得してそこから先頭に存在する1〜3桁の数字を取得。全角文字が混入している場合にそなえて数字をすべて全角→半角変換。これらの数(おそらく連番)で途中に抜けがないかチェックします。

実行にはShane StanleyのAppleScript Libraries「BridgePlus」「MetaData Lib」のインストールが必要です。

連番リストの作成や全角→半角変換、Spotlight検索などをScript Librariesの機能に依存して書いています。また、集合(NSMutableSet)を扱えることで非常に高度な処理を完結に記述できています。処理速度も非常に高速です。

 127ファイルの連番チェック処理:0.113457024097 sec.
 449ファイルの連番チェック処理:0.381642997265 sec.

(結果はMacBook Pro Retina 2012 Core i7 2.66GHz+8GBでの所用時間)

AppleScript名:指定フォルダ以下のテキストファイルのファイル名冒頭についている数字から欠番を求める
– Created 2017-09-04 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use framework “Foundation”
use scripting additions
use mdLib : script “Metadata Lib” version “1.0.0″
use bPlus : script “BridgePlus”
–http://piyocast.com/as/archives/4803

property SMSForder : a reference to current application’s SMSForder
property NSMutableArray : a reference to current application’s NSMutableArray
property NSMutableSet : a reference to current application’s NSMutableSet
property NSIndexSet : a reference to current application’s NSIndexSet
property NSRegularExpressionDotMatchesLineSeparators : a reference to current application’s NSRegularExpressionDotMatchesLineSeparators
property NSRegularExpressionAnchorsMatchLines : a reference to current application’s NSRegularExpressionAnchorsMatchLines
property NSRegularExpression : a reference to current application’s NSRegularExpression
property NSString : a reference to current application’s NSString

load framework –BridgePlus’s force framework loading command

–選択したフォルダ以下のPlain TextをすべてSpotlightで求める(POSIX path list)
set theFolder to (choose folder)
set aRes to retMissingNumberFromEachFiles(theFolder, {“public.plain-text”}) of me
–>  {}

on retMissingNumberFromEachFiles(theFolder, fileTypeList)
  set theFiles to mdLib’s searchFolders:{theFolder} searchString:“kMDItemContentType IN[c] %@” searchArgs:fileTypeList
  
if theFiles = {} then return
  
  
–取得したPOSIX Pathのリストからファイル名の部分のみ抽出
  
set anArray to NSMutableArray’s arrayWithArray:theFiles
  
set bArray to (anArray’s valueForKeyPath:“lastPathComponent”) as list
  
  
–各ファイルの名称の冒頭から1〜3桁 の数字を取り出して、全角–>半角変換を行いつつリストに追加
  
set nArray to NSMutableArray’s new()
  
repeat with i in bArray
    set j to contents of i
    
set aRes to (my findPattern:“^\\d{1,3}” inString:j)
    
if aRes is not equal to {} then
      set jj to (contents of first item of aRes) as string
      
set jj2 to (SMSForder’s transformedFrom:jj ICUTransform:“Fullwidth-Halfwidth” inverse:false) as integer
      (
nArray’s addObject:jj2)
    end if
  end repeat
  
  
–最大値、最小値をもとに連番リストを作成し、ファイル名から得られた配列データとの補集合を求める
  
set maxRes to (nArray’s valueForKeyPath:“@max.self”)’s intValue()
  
set minRes to (nArray’s valueForKeyPath:“@min.self”)’s intValue()
  
  
–最小値から最大値までの連番リスト作成
  
set theIndexSet to NSIndexSet’s indexSetWithIndexesInRange:{minRes, maxRes}
  
set theList to (SMSForder’s arrayWithIndexSet:theIndexSet) as list
  
  
set aSet to NSMutableSet’s setWithArray:theList
  
set bSet to NSMutableSet’s setWithArray:nArray
  
aSet’s minusSet:bSet –補集合
  
  
return (aSet’s allObjects() as list)
  
end retMissingNumberFromEachFiles

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

★Click Here to Open This Script 

2017/09/02 Finder上で選択中のファイルの中に書かれているタイトルに基づいてリネーム

Finder上で選択中のテキストファイルの中に書かれているタイトルを正規表現による検索で検出して、ファイル名に指定するAppleScriptです。

技術的にはぜんぜん見るべき点がありませんが、そこそこ役立つ見本です(ありものをつなぎ合わせた書き捨てレベル)。

「小説を読もう」サイトからKnight’s and Magicのテキストを(テキストエンコーディングにUTF-8を指定して)ダウンロードしてきたら、

text_download.png

ファイル名が、

filename_before.png

のようになっていたので、これを、

filename_after.png

のようにリネームしたいと考え、各テキストファイル中に書かれているタイトル部分を、

file_contents.png

正規表現で検索して指定してみました。ほとんどテキストの1行目にタイトルが書かれているので、1行目の内容をファイル名にしてもよかったのですが、

knight_magic4.png

このように(↑)たまにイレギュラーなファイルが存在しているので、正規表現でサーチしています。

実際にテストしてみたところ、全角文字の「#」と半角文字の「#」が登場していたので、全角文字で検索して存在しないようであれば半角文字で再検索するようになっています。

数百個ぐらいのファイルの処理だと、このようにFinderからselectionを拾ってきても十分なところですが、数千個以上になると別の方法を考えるべきでしょう。

AppleScript名:Finder上で選択中のファイルの中に書かれているタイトルに基づいてリネーム
– Created 2017-09-01 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4797

property NSRegularExpressionDotMatchesLineSeparators : a reference to current application’s NSRegularExpressionDotMatchesLineSeparators
property NSRegularExpressionAnchorsMatchLines : a reference to current application’s NSRegularExpressionAnchorsMatchLines
property NSRegularExpression : a reference to current application’s NSRegularExpression
property NSString : a reference to current application’s NSString

tell application “Finder”
  set aSel to selection as alias list
  
if aSel = {} or aSel = “” then return
end tell

repeat with i in aSel
  set aStr to (read i as «class utf8»)
  
  
–全角のタイトルマークでタイトルを検索
  
set aList to retHeaders(aStr, “#”) of me
  
if aList is not equal to {} then
    set newName to (contents of first item of aList)
    
tell application “Finder”
      set name of i to newName & “.txt”
    end tell
  else
    –半角のタイトルマークでタイトルを検索
    
set bList to retHeaders(aStr, “#”) of me
    
if bList is not equal to {} then
      set newName to (contents of first item of bList)
      
tell application “Finder”
        set name of i to newName & “.txt”
      end tell
    else
      log {i as string}
    end if
  end if
end repeat

on retHeaders(aCon, aHeaderMark)
  set tList to {}
  
set regStr to “^” & aHeaderMark & “{1,6}[^” & aHeaderMark & “]*?$”
  
  
set headerList to my findPattern:regStr inString:aCon
  
repeat with i in headerList
    set j to contents of i
    
set regStr2 to “^” & aHeaderMark & “{1,6}[^” & aHeaderMark & “]*?”
    
set headerLevel to length of first item of (my findPattern:regStr2 inString:j)
    
set the end of tList to (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:

★Click Here to Open This Script 

2016/12/20 OgreKit.frameworkを呼び出して正規表現処理を行う

正規表現の機能を提供するOgreKit.framework(オウガキット)をAppleScriptから呼び出すじっけんです。
ogrekitlogo.png

ASOCを使い始めて真っ先にテストしていたのですが、当時はまだObjective-CからAppleScriptへの「翻訳」のスキルが高くなかったので動くようにできませんでした。さすがに今だと問題なく翻訳できます。

本AppleScriptを構文確認・実行するには、OgreKit.frameworkを~/Library/Frameworksフォルダに入れておく必要があります。ただし、OgreKit自体GitHubで配布されているソースをビルドするといいのか、どこかで配布されているバイナリを拾ってきたのか、入手経路についてはあまり記憶が定かではありません(けっこう放置していたので)。ちなみに、確認はバージョン2.0で行いました。

OgreKitをはじめCocoa+Objective-Cから使える正規表現のフレームワークは割といろいろ試していますが、テキストの抽出や置換が目的であって、GUIアプリケーション上のオブジェクトをこれで抽出できるというものではありません。そこはフィルタ参照を用いることになります。

AppleScript名:OgreKitで文字列中の正規表現にマッチした部分を順番に得る
– Created 2016-12-20 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “OgreKit”
–http://piyocast.com/as/archives/4368
–http://sonoisa.github.io/ogrekit/HowToUse2.html

set regEx to current application’s OGRegularExpression’s regularExpressionWithString:“a[^a]*a”
set enum to regEx’s matchEnumeratorInString:“alphabetagammadelta”

set aResArray to current application’s NSMutableArray’s alloc()’s init()
repeat
  set aMatch to enum’s nextObject()
  
if aMatch = missing value then exit repeat
  
set hitStr to aMatch’s matchedString()
  
aResArray’s addObject:hitStr
end repeat

return aResArray as list
–>  {”alpha”, “aga”, “adelta”}

★Click Here to Open This Script 

AppleScript名:OgreKitで文字列を正規表現にマッチした部分で分割する
– Created 2016-12-20 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “OgreKit”
–http://piyocast.com/as/archives/4368
–http://sonoisa.github.io/ogrekit/HowToUse2.html

set regEx to current application’s OGRegularExpression’s regularExpressionWithString:“\\s*,\\s*”
set dRes to (regEx’s splitString:“36.5C, 3.8C, -195.8C”) as list
–> {”36.5C”, “3.8C”, “-195.8C”}

★Click Here to Open This Script 

AppleScript名:OgreKitで文字列中の正規表現にマッチした部分すべてを置換する
– Created 2016-12-20 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “OgreKit”
–http://piyocast.com/as/archives/4368
–http://sonoisa.github.io/ogrekit/HowToUse3.html

set regEx to current application’s OGRegularExpression’s regularExpressionWithString:“a[^a]*a”
set dRes to (regEx’s replaceAllMatchesInString:“alphabetagammadelta” withString:“(\\0)”) as string
–>  ”(alpha)bet(aga)mm(adelta)”

★Click Here to Open This Script 

AppleScript名:OgreKitでNSStringに対して検索を行い、最初にマッチした範囲を返す
– Created 2016-12-20 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “OgreKit”
–http://piyocast.com/as/archives/4368
–http://sonoisa.github.io/ogrekit/HowToUse6.html

set aStr to current application’s NSString’s stringWithString:“alphabetagammadelta”
set matchedRange to aStr’s rangeOfRegularExpressionString:“a[^a]*a”
return matchedRange as record
–>  {location:0, length:5}

★Click Here to Open This Script 

AppleScript名:OgreKitでNSMutableStringに対して検索・置換を行う
– Created 2016-12-20 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “OgreKit”
–http://piyocast.com/as/archives/4368
–http://sonoisa.github.io/ogrekit/HowToUse6.html

set aStr to current application’s NSMutableString’s stringWithString:“alphabetagammadelta”
aStr’s replaceOccurrencesOfRegularExpressionString:“a[^a]*a” withString:“(\\0)” options:1 range:(current application’s NSMakeRange(0, aStr’s |length|()))
aStr as string
–>  ”(alpha)bet(aga)mm(adelta)”

★Click Here to Open This Script 

AppleScript名:OgreKitで指定テキストファイルの改行コードを統一する
– Created 2016-12-20 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “OgreKit”
use textParser : script “japaneseTextEncodingDetector”
–http://piyocast.com/as/archives/4368
–http://sonoisa.github.io/ogrekit/HowToUse5.html

property OgreNonbreakingNewlineCharacter : -1 –(無改行)
property OgreUnixNewlineCharacter : 0 –LF(Unix)
property OgreLfNewlineCharacter : 0 –LF(Unix)
property OgreMacNewlineCharacter : 1 –CR(Mac)
property OgreCrNewlineCharacter : 1 –CR(Mac)
property OgreWindowsNewlineCharacter : 2 –CR+LF(Windows)
property OgreCrLfNewlineCharacter : 2 –CR+LF(Windows)

set aFile to POSIX path of (choose file)
set aText to readJapanesTextFileWithGuessingEncoding(aFile) of textParser –日本語テキストエンコーディング自動判定ライブラリで判定
set cText to current application’s OGRegularExpression’s replaceNewlineCharactersInString:aText withCharacter:OgreLfNewlineCharacter

★Click Here to Open This Script 

2015/10/13 数字以外を削除して返す

与えられた文字列のうち、数字以外を削除して返すAppleScriptです。

正規表現で数字以外をヌルに置換して返します。スペースを許容するものと、スペースと改行を許容するものも作っておきました。

後で加工することを考えると、地味にスペースも取り出せると処理が楽だったので、、、

AppleScript名:ASOCで数字以外を削除して返す
– Created 2015-10-13 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

set aStr to "<< 98158999992
>>"

set bStr to returnNumberCharsOnly(aStr)
–>  "98158999992"

set cStr to returnNumberCharsAndSpaceOnly(aStr)
–> " 98158999992 "

set dStr to returnNumberCharsAndSpaceAndReturnOnly(aStr)
–>
(*
" 98158999992
"
*)

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 returnNumberCharsAndSpaceOnly(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 returnNumberCharsAndSpaceOnly

on returnNumberCharsAndSpaceAndReturnOnly(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 returnNumberCharsAndSpaceAndReturnOnly

★Click Here to Open This Script 

2015/09/28 NSPredicateによる正規表現を利用した部分一致抽出

Cocoaの機能を用いて、リストのレコードから正規表現による要素の抽出を行うAppleScriptです。実行にはShane StanleyのAppleScript Library「BridgePlus」のインストールが必要になります。

NSPredicateや複数条件を併記するNSCompoundPredicateで、リストに入ったレコードを抽出するのが楽すぎて、もはやこれなしにデータの抽出ができなくなりつつあります。ゴリゴリにPure AppleScriptで記述できないこともないのですが、NSPredicateが使えるだけでもASOCを覚える価値があると思えるほどです。

そんな中、徐々に記述レベルが上がってくると、無茶な抽出にもチャレンジしてみたくなるもので・・・

  「98」で始まる8桁の数字が入った要素をリストアップ。ただし、途中に出現するパターンも含む

という抽出にチャレンジしてみました(仕事で必要だったもので)。完全一致ならなんとか書けるのですが、上記の条件の文字列を「含む」ものまで抽出することを考えると、とたんに難易度が上がります。

結局、AppleのPredicate Programming Guideを総チェックして、「.*」で囲めば「条件に合うものを含む」という状態を抽出できることを見つけました(プログラミングではなくて、情報収集とかパズルのレベルですね)。

AppleScript名:ASOCでNSPredicateによる正規表現を併用した抽出
– Created 2015-09-28 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use BridgePlus : script “BridgePlus” —Shane Stanley’s BridgePlus

set sampleList to {{textData:“Piyomaru”, uID:1}, {textData:“Xx Piyomaru x”, uID:2}, {textData:“xxxxx 11111111 98 x xxxxxxxx.”, uID:3}, {textData:“98x Xxxxxx (xx xxxxxxxxxx)”, uID:4}, {textData:“< < 98158113 >>”, uID:5}, {textData:“#98158084 Xxxxx Xxxxx xxxx”, uID:6}, {textData:“#98158084 Xxxxx Xxxxx xxxx”, uID:7}, {textData:“Office # 98158107″, uID:8}, {textData:“ID#98158087″, uID:9}, {textData:“98158089″, uID:10}, {textData:“00158098″, uID:11}}

–全文一致で抽出
set aRes to my filterRecListByLabel1(sampleList, “textData == ’Piyomaru’”)
–>  {​​​​​{​​​​​​​textData:”Piyomaru”, ​​​​​​​uID:1​​​​​}​​​}

–部分一致で抽出
set bRes to my filterRecListByLabel1(sampleList, “textData contains ’Piyomaru’”)
–>  {​​​​​{​​​​​​​textData:”Piyomaru”, ​​​​​​​uID:1​​​​​}, ​​​​​{​​​​​​​textData:”Xx Piyomaru x”, ​​​​​​​uID:2​​​​​}​​​}

–正規表現で抽出(8桁の数字)
set cRes to my filterRecListByLabel1(sampleList, “textData MATCHES ’\\\\d{8}’”)
–>  {​​​​​{​​​​​​​textData:”98158089″, ​​​​​​​uID:10​​​​​}, ​​​​​{​​​​​​​textData:”00158089″, ​​​​​​​uID:11​​​​​}​​​}

set dRes to my filterRecListByLabel1(sampleList, “textData MATCHES ’98\\\\d{6}’”)
–>  {​​​​​{​​​​​​​textData:”98158089″, ​​​​​​​uID:10​​​​​}​​​}

set eRes to my filterRecListByLabel1(sampleList, “textData LIKE ’*98??????*’”)
–>  {​​​​​{​​​​​​​textData:”xxxxx 11111111 98 x xxxxxxxx.”, ​​​​​​​uID:3​​​​​}, ​​​​​{​​​​​​​textData:”98x Xxxxxx (xx xxxxxxxxxx)”, ​​​​​​​uID:4​​​​​}, ​​​​​{​​​​​​​textData:”< < 98158113 >>”, ​​​​​​​uID:5​​​​​}, ​​​​​{​​​​​​​textData:”#98158084 Xxxxx Xxxxx xxxx”, ​​​​​​​uID:6​​​​​}, ​​​​​{​​​​​​​textData:”#98158084 Xxxxx Xxxxx xxxx”, ​​​​​​​uID:7​​​​​}, ​​​​​{​​​​​​​textData:”Office # 98158107″, ​​​​​​​uID:8​​​​​}, ​​​​​{​​​​​​​textData:”ID#98158087″, ​​​​​​​uID:9​​​​​}, ​​​​​{​​​​​​​textData:”98158089″, ​​​​​​​uID:10​​​​​}​​​}

set fRes to my filterRecListByLabel1(sampleList, “textData LIKE ’*\”98\”[0-9][0-9][0-9][0-9][0-9][0-9]*’”) –Oops!!
–>  {}

set gRes to my filterRecListByLabel1(sampleList, “textData LIKE ’*[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]*’”) –Oops!!
–>  {}

set hRes to my filterRecListByLabel1(sampleList, “textData MATCHES ’.*[98]\\\\d{6}.*’”) –OK!!
–>  {​​​​​{​​​​​​​textData:”< < 98158113 >>”, ​​​​​​​uID:5​​​​​}, ​​​​​{​​​​​​​textData:”#98158084 Xxxxx Xxxxx xxxx”, ​​​​​​​uID:6​​​​​}, ​​​​​{​​​​​​​textData:”#98158084 Xxxxx Xxxxx xxxx”, ​​​​​​​uID:7​​​​​}, ​​​​​{​​​​​​​textData:”Office # 98158107″, ​​​​​​​uID:8​​​​​}, ​​​​​{​​​​​​​textData:”ID#98158087″, ​​​​​​​uID:9​​​​​}, ​​​​​{​​​​​​​textData:”98158089″, ​​​​​​​uID:10​​​​​}​​​}

–リストに入れたレコードを、指定の属性ラベルの値で抽出
on filterRecListByLabel1(aRecList as list, aPredicate as string)
  set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aPredicate
  
set filteredArray to aArray’s filteredArrayUsingPredicate:aPredicate
  
set bList to ASify from filteredArray as list
  
return bList
end filterRecListByLabel1

★Click Here to Open This Script 

2015/09/14 ASOCで文字種別を判定する v2

Cocoaの機能を用いて、文字種別を判定するAppleScriptです。

記号(Symbol)のチェックと、製品コードなどのルールを供給するとそのとおりの文字の並びになっているかをチェックするルーチンを加えました。

これまでに作ってきた同種のルーチンは以下のようになります。

コードのチェック
簡易マクロ展開つきのコードチェック v1

わざわざ文字を分解するよりも、ルールから正規表現の文字列を組み立てて、そのとおりになっているかのチェックを行うほうがよいかも・・・とは考えたのですが、どの桁にエラーがあるかを確認するためにはこのような仕様のほうがよいだろうか、と思うものです。

AppleScript名:ASOCで文字種別を判定する v2
– Created 2015-09-14 by Takaaki Naganoya
– 2015 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

–http://core-tech.jp/gijutsublog/2014/06/23/10505

set aRes to my chkStrings:“0123-45-678A” asRule:“9999-99-999X”
–>  {​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true​​​}
set bRes to my chkStrings:“0123-45-d2aA” asRule:“9999-99-999X”
–>  {​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​true, ​​​​​false, ​​​​​true, ​​​​​false, ​​​​​true​​​}

–指定したルールのとおりの文字種の並びになっているか?
on chkStrings:aStr asRule:aRuleStr
  set aList to characters of aStr
  
set rList to characters of aRuleStr
  
if (length of aList is not equal to length of rList) then return false
  
  
set chkList to {}
  
repeat with i from 1 to (length of aList)
    set j1 to contents of item i of aList
    
set j2 to contents of item i of rList
    
    
if j2 = “9″ then
      set j3 to (my chkNumeric:j1)
    else if j2 = “X” then
      set j3 to (my chkAlphabet:j1)
    else if j2 is in {“$”, “\”", “!”, “~”, “&”, “=”, “#”, “[”, “]”, “.”, “_”, “-”, “+”, “`”, “|”, “{”, “}”, “?”, “%”, “^”, “*”, “/”, “’”, “@”, “-”, “/”, “:”, “;”, “(”, “)”, “,”} then
      set j3 to (my chkSymbol:j1)
    end if
    
set the end of chkList to j3
  end repeat
  
return chkList
end chkStrings:asRule:

– アルファベットのみか
on chkAlphabet:checkString
  set aStr to current application’s NSString’s stringWithString:checkString
  
set allCharSet to current application’s 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 chkNumeric:checkString
  set digitCharSet to current application’s NSCharacterSet’s characterSetWithCharactersInString:“0123456789″
  
set ret to my chkCompareString:checkString baseString:digitCharSet
  
return ret as boolean
end chkNumeric:

–アルファベットと数字のみか
on chkAlphaNumeric:checkString
  set alnumCharSet to current application’s NSCharacterSet’s alphanumericCharacterSet()
  
set ret to my chkCompareString:checkString baseString:alnumCharSet
  
return ret as boolean
end chkAlphaNumeric:

–アルファベットと数字と記号のみか
on chkAlphaNumericSymbol:checkString
  set muCharSet to current application’s NSCharacterSet’s alphanumericCharacterSet()’s mutableCopy()
  
muCharSet’s addCharactersInString:“$\”!~&=#[]._-+`|{}?%^*/’@-/:;(),”
  
set ret to my chkCompareString:checkString baseString:muCharSet
  
return ret as boolean
end chkAlphaNumericSymbol:

–記号のみか
on chkSymbol:checkString
  set muCharSet to current application’s NSCharacterSet’s alloc()’s init()
  
muCharSet’s addCharactersInString:“$\”!~&=#[]._-+`|{}?%^*/’@-/:;(),”
  
set ret to my chkCompareString:checkString baseString:muCharSet
  
return ret as boolean
end chkSymbol:

–全角文字が存在するか
on chkMultiByteChar:checkString
  set aStr to current application’s NSString’s stringWithString:checkString
  
set aRes to aStr’s canBeConvertedToEncoding:(current application’s NSASCIIStringEncoding)
  
return (aRes as boolean)
end chkMultiByteChar:

on chkCompareString:checkString baseString:baseString
  set aScanner to current application’s 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 

2015/09/09 続・伏字文字列を作成する

ここに掲載するScriptのMACアドレスやらシリアル番号やらを伏字にするため「だけに」作ったAppleScript。1行のテキストのうちの数文字だけ伏字にするため、速度も何も重視していなかったほぼ作り捨てレベルのこのScriptに技術的な課題を見出してしまった人物がいます。

Shane Stanleyです。なぜだ〜(^ー^;;。

自分が作ったPure AppleScript版(A)、Shaneによる英数字しか考慮していないASOC版(B)、Shaneによる2バイト文字も考慮したASOC版(C)の3つのバージョンが登場。

実行速度は、

 (A):◼️◼️ 0.0001976 sec
 (B):◼️◼️◼️◼️ 0.00038 sec
 (C):◼️◼️◼️◼️◼️◼️◼️◼️◼️◼️ 0.001 sec

ぐらい違います(◼️が少ない方が速い)。

v3vsv4.png

伏字と考えると分かりにくいですが(日本語の伏字は、「伏せてもなぜか意味が分かる、ハイレベルな文芸活動」だったりするので)、「文字グループを指定した文字置換」ととらえると、技術的にツッコミを入れるべき対象に見えるかもしれません。

AppleScript名:伏字文字列を作成する v3
– Created 2015-09-09 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aStr to “This is my secret information.”
set bStr to makeUnprintableChars(aStr)
–>  ”Xxxx xx xx xxxxxx xxxxxxxxxxx.”

on makeUnprintableChars(aStr)
  set anNSString to current application’s NSString’s stringWithString:aStr
  
set anNSString to anNSString’s stringByReplacingOccurrencesOfString:“[A-Z0-9]” withString:“X” options:(current application’s NSRegularExpressionSearch) range:{0, anNSString’s |length|()}
  
set anNSString to anNSString’s stringByReplacingOccurrencesOfString:“[a-z]” withString:“x” options:(current application’s NSRegularExpressionSearch) range:{0, anNSString’s |length|()}
  
return anNSString as text
end makeUnprintableChars

★Click Here to Open This Script 

AppleScript名:伏字文字列を作成する v4
– Created 2015-09-09 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set aStr to “これは秘密のInformationです。” –Alphabet & Numeric以外でも置換可能
set bStr to makeUnprintableChars(aStr)
–>  ”xxxxxxXxxxxxxxxxxxx。”

on makeUnprintableChars(aStr)
  set anNSString to current application’s NSString’s stringWithString:aStr
  
set anNSString to anNSString’s stringByReplacingOccurrencesOfString:“[\\p{Lu}\\p{Lt}\\p{Nd}]” withString:“X” options:(current application’s NSRegularExpressionSearch) range:{0, anNSString’s |length|()}
  
set anNSString to anNSString’s stringByReplacingOccurrencesOfString:“[\\p{Ll}\\p{Lm}\\p{Lo}]” withString:“x” options:(current application’s NSRegularExpressionSearch) range:{0, anNSString’s |length|()}
  
return anNSString as text
end makeUnprintableChars

★Click Here to Open This Script 

2014/11/22 Keynote 6.5で各スライドのタイトル、マスタースライド名を取得してデータ化

Keynote 6.5で作成中の書類(開発中のシステムの仕様書)から、各ページタイトルを取得。さらに各スライド(ページ)のマスタースライド名を取得しておいて、目次の文字の大きさに違いをつけようとして、データを作成するAppleScriptの試作品です。

実際に、一般的なアプリケーションであるKeynoteをコントロールしつつ、データ処理をASOCベースのサブルーチンで行わせてみました。

40ページ強あるKeynoteのデータの処理を1秒以下で行えます(MacBook Pro Retina mid 2012)。

また、各スライド(ページ)のマスタースライド名については、出現頻度をカウントし、出現頻度の少ないものが各章のトビラであると仮定して、各タイトルにレベル設定(トビラページは1、通常ページは2)を行っています。

実際に、OmniGraffleではこうした処理(各ページのタイトルを求めて、目次ページを動的に作成してすべての項目に実際のページへのリンクを設定)をすべてAppleScriptで行っていますが(PDFに書き出したときに便利)・・・OmniGraffleで行うよりも、(ユーザーの多い)Keynote上で行えたほうが便利です。

Keynote 6.5もけっこうAppleScript対応機能が向上してきてはいるのですが、たとえばテキストオブジェクトを生成しても・・・左寄せ/センタリング/右寄せを指定できないとか、フォントと文字サイズも設定できないとか(ここはまだ試行錯誤の余地あり)、他のスライド(ページ)へのリンク設定が行えないなど、実践的な処理を記述するためには「いまひとつ」な印象です。

とはいえ、Keynote 6.5のAppleScript用語辞書を見ていると、オブジェクトにScript Labelを(将来的に)設定できるように考えられているなど、楽しみです(グラフ方面は手付かずだったり)。

Keynoteには、早く完全体になってほしいところです(永遠に未完成ということはないと思いたいですが、なかなか進捗しないですね)。

ちなみに、Keynoteへのtellブロック内でcocoaの機能にアクセスするコードを直接書くと、実行時にScript Editorがクラッシュするなどなかなかたいへんでした。ASOCのコードは通常のアプリケーションへのアクセスと分けたほうが得策のようです。

AppleScript名:keynote_test
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

property sPage : 3 –1ページ目が表紙、2ページ目が目次として3ページ目以降を処理対象とする

–最前面のドキュメントを取得する
tell application “Keynote”
  set aDocRef to front document
end tell

set tList to retKeynoteSlideTitleFrom(3, aDocRef) of me
–> {”システム概要”, “ハードウェア構成概要”, “ソフトウェア構成概要”,….}

–ドキュメント中の3ページ以降のslideの各マスタースライドの名称を取得して、各マスタースライドの出現頻度を取得して昇順ソート
set bList to retKeynoteSlideBaseSlideNameFrom(sPage, aDocRef) of me
–> {{”タイトル(中央)”, 8}, {”タイトル(上)”, 32}}–{マスタースライド名, 登場頻度カウント}

set {masterList, freqList} to conv2dListTo1dLists(bList) of me
–> {{”タイトル(中央)”, “タイトル(上)”}, {8, 32}}

set tOutList to {}

tell application “Keynote”
  tell aDocRef
    repeat with i from sPage to (count every slide)
      tell slide i
        –Title
        
set aIndex to (i - sPage + 1)
        
set aTitle to contents of item aIndex of tList
        
        
–Base Slide
        
set aBaseName to name of base slide
        
set aLevel to offseOfList(masterList, aBaseName) of me
        
        
set the end of tOutList to {aTitle, aLevel}
        
      end tell
    end repeat
  end tell
end tell

return tOutList
–> {{”システム概要”, 1}, {”ハードウェア構成概要”, 2}, {”ソフトウェア構成概要”, 2}, {”プログラム概要”, 2}, {”プログラム呼称一覧”, 2}, ….}
–1=マスタースライドが”タイトル(中央)”
–2=マスタースライドが “タイトル(上)”

–List中の指定項目の出現位置を返す(複数ヒットした場合には最初の項目番号)。1はじまりのAS仕様のインデックスを使用
on offseOfList(aList, anItem)
  set aResList to (current application’s SMSFord’s indexesOfItem:anItem inArray:(aList) inverting:false) as list
  
set aRes to (first item of aResList)
  
return (aRes + 1)
end offseOfList

–指定ページ(slide)から末尾までの各ページのTitleを取得して返す
on retKeynoteSlideTitleFrom(startPageNum as integer, aDocRef)
  
  
set tList to {}
  
  
try
    –全ページ(slide)のtitleを一括で取得するのが一番高速
    
tell application “Keynote”
      tell aDocRef
        set sCount to count every slide
        
        
if sCount < startPageNum then error
        
if startPageNum < 1 then return {}
        
        
set tList to object text of default title item of every slide
      end tell
    end tell
  on error
    return {}
  end try
  
  
–取得したタイトルから、前後の改行、前後の空白、および途中に入っている改行を削除する
  
set t2List to {}
  
repeat with i in tList
    set j to contents of i
    
set jj to cleanUpText(j) of me
    
set jj to cleanUpCRLF(jj) of me
    
set the end of t2List to jj
  end repeat
  
  
  
  
–一括取得した内容を適宜加工して返すのがベスト
  
set ttList to contents of items startPageNum thru -1 of t2List
  
  
return ttList
  
end retKeynoteSlideTitleFrom

–文字列の前後の改行と空白文字を除去
on cleanUpText(someText)
  set theString to current application’s NSString’s stringWithString:someText
  
set theString to theString’s stringByReplacingOccurrencesOfString:” +” withString:” “ options:(current application’s NSRegularExpressionSearch) range:{location:0, |length|:length of someText}
  
set theWhiteSet to current application’s NSCharacterSet’s whitespaceAndNewlineCharacterSet()
  
set theString to theString’s stringByTrimmingCharactersInSet:theWhiteSet
  
return theString as text
end cleanUpText

–文字置換
on repChar(aStr, targStr, repStr)
  set aString to current application’s NSString’s stringWithString:aStr
  
set bString to aString’s stringByReplacingOccurrencesOfString:targStr withString:repStr
  
set cString to (bString’s ASify()) as string
  
return cString
end repChar

–指定文字列からCRLFを除去
on cleanUpCRLF(aStr)
  set aString to current application’s NSString’s stringWithString:aStr
  
set bString to aString’s stringByReplacingOccurrencesOfString:(string id 10) withString:“” –remove LF
  
set cString to bString’s stringByReplacingOccurrencesOfString:(string id 13) withString:“” –remove CR
  
set dString to (cString’s ASify()) as string
  
return dString
end cleanUpCRLF

–2D Listを1D Listに変換
on conv2dListTo1dLists(aList as list)
  
  
set newList to {}
  
set aLen to length of first item of aList
  
  
repeat aLen times
    set the end of newList to {}
  end repeat
  
  
  
repeat with i in aList
    repeat with ii from 1 to aLen
      set the end of item ii of newList to (item ii of i)
    end repeat
  end repeat
  
  
return newList
  
end conv2dListTo1dLists

–指定ページ(slide)から末尾までの各ページのbase slide名を取得してユニーク化して返す
on retKeynoteSlideBaseSlideNameFrom(startPageNum, aDocRef)
  
  
set aList to {}
  
  
tell application “Keynote”
    tell aDocRef
      set sCount to count every slide
      
if sCount < startPageNum then error
      
      
repeat with i from startPageNum to sCount
        tell slide i
          set aBase to name of base slide
          
set the end of aList to aBase
        end tell
      end repeat
      
    end tell
  end tell
  
  
  
–結果をユニーク化する
  
set aArray to current application’s NSArray’s arrayWithArray:aList
  
set bArray to aArray’s valueForKeyPath:“@distinctUnionOfObjects.self”
  
  
set bList to bArray’s ASify() as list
  
–> {1, 1.1, 2, 3, 4}
  
  
  
–Base Slide名の出現頻度を調べる
  
set cList to {}
  
  
repeat with i in bList
    set aName to contents of i
    
set aCount to countSpecifiedItem(aList, aName) of me
    
set the end of cList to {aName, aCount}
  end repeat
  
  
  
–出現頻度(aCount)をキーにして昇順ソート
  
set dList to sort2DList(cList, 2, {true}) of me
  
  
return dList
  
end retKeynoteSlideBaseSlideNameFrom

–リスト中の指定項目の出現回数を返す
on countSpecifiedItem(aList, countItem)
  set aRes to (current application’s SMSFord’s indexesOfItem:countItem inArray:aList inverting:false) as list
  
set aCount to length of aRes
  
return aCount
end countSpecifiedItem

–2D Listをソート
on sort2DList(aList as list, sortIndexes as list, sortOrders as list)
  
  
–index値をAS流(アイテムが1はじまり)からCocoa流(アイテムが0はじまり)に変換
  
set newIndex to {}
  
repeat with i in sortIndexes
    set j to contents of i
    
set j to j - 1
    
set the end of newIndex to j
  end repeat
  
  
–Sort TypeのListを作成(あえて外部から指定する内容でもない)
  
set sortTypes to {}
  
repeat (length of sortIndexes) times
    set the end of sortTypes to “compare:”
  end repeat
  
  
–Sort
  
set resList to (current application’s SMSFord’s subarraysIn:(aList) sortedByIndexes:newIndex ascending:sortOrders sortTypes:sortTypes |error|:(missing value)) as list
  
  
return resList
  
end sort2DList

★Click Here to Open This Script 

2014/11/22 リストから抽出(asoc)

リストから条件に合う項目だけを抽出するAppleScriptです。

こういう感じで1D Listだけでなく2D Listを抽出できると便利でしょう。ああ、2D Listの抽出をやりたい、、、

AppleScript名:listの項目をフィルタリング(項目文字長)
– Created 2014-11-21 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aList to {“piyomaru”, “Piyomaru Software”, “Naganoya”, “Takaaki”, “MacBook Pro Retina mid 2012″}

set bList to filterListUsingPredicate(aList, “length > 8″) –文字列長が8文字より長い項目を返す
–> {”MacBook Pro Retina mid 2012″, “Piyomaru Software”}

set cList to filterListUsingPredicate(aList, “SELF MATCHES ’.*e$’”) –正規表現で末尾が”e”
–> {”Piyomaru Software”}

set dList to filterListUsingPredicate(aList, “SELF LIKE ’piyo*’”)
–> {”piyomaru”}

set eList to filterListUsingPredicate(aList, “SELF LIKE[c] ’piyo*’”)
–> {”piyomaru”, “Piyomaru Software”}

set fList to filterListUsingPredicate(aList, “SELF CONTAINS[c] ’Piyo’”)
–> {”piyomaru”, “Piyomaru Software”}

on filterListUsingPredicate(aList as list, aPredicateStr as string)
  –ListからNSArrayへの型変換
  
set setKey to current application’s NSMutableSet’s setWithArray:aList
  
  
–抽出
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aPredicateStr
  
set aRes to (setKey’s filteredSetUsingPredicate:aPredicate)
  
set bRes to aRes’s allObjects()
  
  
–NSArrayからListに型変換して返す
  
set cRes to (bRes’s ASify()) as list
  
return cRes
end filterListUsingPredicate

★Click Here to Open This Script 

2014/11/22 レコードのリストから抽出(asoc)

レコードのリスト(List化したRecord)から、条件に合う項目を抽出するAppleScriptです。

本来、AppleScriptの処理系だけで、

  set aList to {{aName:”ぴよまる”, weight:70}, {aName:”ぴよこ”, wight:60}}

のようなリスト化したレコードをフィルタ参照(set bList to every record whose cell “aName” is equal to “ぴよまる” とか)で抽出できるべきですが、長年搭載されてきませんでした。

こうした処理をAppleScriptで実現するためには、FileMaker Proなどのデータベースを併用するか、OS標準搭載のDataBase Events(フィルタ参照で該当データを抽出する専用のDBシステム。ソート機能がない)を利用することに(ASだけでゴリゴリ記述するという方向性もありますが、、、)。

さすがにDataBase Eventsを使うぐらいなら、ASOCで記述したほうが簡単です。

数百万件や数千万件の規模のデータを扱うにはFileMaker Proなどのデータベースを併用すべきだと思いますが、十万件ぐらいの規模のデータであればAppleScriptだけで便利にデータ処理できてよさそうです。

AppleScript名:asoc_レコードのリストから抽出
– Created 2014-11-21 by Takaaki Naganoya
– 2014 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”

set aRecList to {{aName:“piyoko”, aVal:100}, {aName:“piyomaru”, aVal:80}, {aName:“piyoo”, aVal:10}, {aName:“Gundamo”, aVal:10}}

set bList to filterRecListByLabel(aRecList, “aName like ’Gun*’”) of me
–> {{aName:”Gundamo”, aVal:10}}

set cList to filterRecListByLabel(aRecList, “aVal >=80″) of me
–> {{aName:”piyoko”, aVal:100}, {aName:”piyomaru”, aVal:80}}

set dList to filterRecListByLabel(aRecList, “aVal=80 or aVal=10″) of me
–> {{aName:”piyomaru”, aVal:80}, {aName:”piyoo”, aVal:10}, {aName:”Gundam”, aVal:10}}

set eList to filterRecListByLabel(aRecList, “aVal=10 and aName like ’piyo*’”) of me
–> {{aName:”piyoo”, aVal:10}}

set fList to filterRecListByLabel(aRecList, “aName matches ’.*u$’”) of me –名前の最後が u で終わるものを、正規表現を用いて抽出
–> {{aName:”piyomaru”, aVal:80}}

set eList to filterRecListByLabel(aRecList, “aName == ’piyomaru’”) of me
–> {{aName:”piyomaru”, aVal:80}}

set eList to filterRecListByLabel(aRecList, “aName.length > 6″) of me –指定ラベルのデータの文字列長が6以上
–> {{aName:”piyomaru”, aVal:80}, {aName:”Gundamo”, aVal:10}}

–リストに入れたレコードを、指定の属性ラベルの値で抽出
on filterRecListByLabel(aRecList as list, aPredicate as string)
  –ListからNSArrayへの型変換
  
set aArray to current application’s NSArray’s arrayWithArray:aRecList
  
  
–抽出
  
set aPredicate to current application’s NSPredicate’s predicateWithFormat:aPredicate
  
set filteredArray to aArray’s filteredArrayUsingPredicate:aPredicate
  
  
–NSArrayからListに型変換して返す
  
set bList to (filteredArray’s ASify()) as list
  
return bList
end filterRecListByLabel

★Click Here to Open This Script 

2012/06/13 AppleScript内でperl正規表現を使う

AppleScriptからperlを呼び出して正規表現によるテキスト検索、置換を行うサンプルです。

ごく近しい知り合いからもらったもので、他の人が作ったScriptをラッピングしてあります。

OSAXで正規表現の命令を追加したり、テキストエディタの正規表現の機能を呼び出したりするのもよいのですが、perlなどの他の言語処理系を呼び出した方がシンプルで済むこともあります。

本サンプルは「穴が開くほど使いまくったもの」(本人談)なので、安定度も折り紙付きです。

スクリプト名:AppleScript内でperl正規表現を使う.scpt
set aStr to “xxxx@ezweb.ne.jp”
set aResult to regex_match(“/ezweb/”, aStr) of doPerlKit
log {“aResult = “, aResult}

if aResult is true then
  –set aStr to regex_replace(”/e/A/”, aStr) of me
  
set aStr to regex_replace(“/e/A/g”, aStr) of doPerlKit –gオプションも使える
  
log {“aStr = “, aStr}
end if

——————————————
–AppleScript内でPerl正規表現を使用するためのルーチン群
——————————————
script doPerlKit
  
  
–★targetがregexにマッチするか判定
  
on regex_match(regex, target)
    set command to “$target = q/” & target & “/; if ($target =~ “ & regex & “){ print q/1/; }else{ print q/0/; }” –※引数を使用してperlコマンド生成
    
set str_result to exec_perl(command) of me
    
return evalResult(str_result) of me
  end regex_match
  
  
–★targetをregexで置換
  
on regex_replace(regex, target)
    set command to “$target = q/” & target & “/; $target =~ s” & regex & “; print $target;” –※引数を使用してperlコマンド生成
    
set str_result to exec_perl(command) of me
    
return evalResult(str_result) of me
    
–return str_result
  end regex_replace
  
  
–★commandをperlで実行
  
on exec_perl(command)
    set one_liner to “perl -e ‘” & command & “‘” as Unicode text
    
    
–エラー処理
    
try
      set str_result to do shell script one_liner –※shell実行
    on error
      set str_result to “syntax error : “ & one_liner –perlがエラーを吐いたとき
    end try
    
return str_result
  end exec_perl
  
  
–★結果がtrueかfalseかerrorか判断
  
on evalResult(str_result)
    if str_result is “1″ then
      return true
    else if str_result begins with “syntax error : “ then
      return str_result
    else
      return false
    end if
  end evalResult
  
end script

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2012/05/06 ASObjC Runner〜ASOCの機能にAppleScript的な記法を

ASObjC Runnerは、ASOCのスクリプト(テキストベース)の実行機能をMac OS X 10.6.xと10.7.xに提供するフリーのアプリケーションです。作者はShane Stanley。ほかにも、Late Nighet SoftwareのMark Alldrittなども協力しているとのこと。

asobjc1.png

ASOCは基本的には、ランタイムアプリケーション上で動作するものですが、ASObjC Runnerではテキストファイル形式のASOCのプログラムをそのまま実行します。

tell application “ASObjC Runner”

のtellブロック内にscriptを記述します。サンプルはmacosxautomation.comに掲載されているので、雰囲気を伺い知ることができます。

asoctable.png

……以前までは、ASobjC Runnerはあまり個人的な興味を喚起されない存在だったのですが、途中のバージョンアップで面白い要素が追加されました。

Cocoaのオブジェクトに対して標準的なAppleScriptの用語でアクセスできるようにする機能です。

正確にいえば……ASObjC Runner側で想定している範囲の機能について、ASObjC RunnerがAppleScript用語辞書を用意しており、その用語を使って一般的なAppleScript的なアプローチでScriptingが行えるというわけです。

AS用語辞書が提供されており、通常のAppleScript的な用語が利用できるのは、

リスト操作(ソートや検索など)、文字列操作(検索、置換、日付文字列書式指定など。正規表現による検索/置換文字列指定の機能を含む)、ファイル操作(情報取得、コピー、削除、移動、ファイル作成、plist読み書き)などのほか、プログレスバー付きダイアログの表示

などです(ASOC的記法で書けば、より広い範囲のCocoaの機能をダイレクトに使用できます)。

2012/02/26 AppleScriptで正規表現(regexp)を

AppleScriptの基本文法に正規表現はありません。それでも、AppleScriptネイティブの機能で文字列加工などは普通にやっているわけですが、正規表現が使えないことに不満を感じているユーザーもいるようです。

でも、AppleScriptで正規表現が「使えない」なんて誰が決めたんでしょう? 標準命令セットに存在していないだけなのに。

(A)OSAXを追加
AppleScriptでは、命令などを追加するプラグイン機構「Scripting Additions」とか「OSAX」と呼ばれる仕組みがあります。

そもそも、標準命令自体が「Standard Additions」OSAXで提供されているほど。昔から正規表現を提供するOSAXは流通しており、使いたい人はインストールして使っているという状態。

ただし、どのユーザー環境にもインストールされているわけではないので、Scriptを配布して広く使ってもらうためには敷居が高いところです(自分専用のScriptであれば、問題はないでしょう)。

有名なところでは、仏Satimage Softwareの「Satimage OSAX」があります。

(B)正規表現を使えるアプリケーションを制御
正規表現を使えるアプリケーションをコントロールすれば、正規表現の使用は可能です。テキストエディタなど、対応しているものは多々あります。

ただ、これもすべてのユーザー環境にインストールされているわけではないので、Scriptを配布して広く使ってもらうという目的には合致していません。

(C)他の言語処理系を呼び出す
他の、正規表現の機能を持つ言語処理系をdo shell script命令で呼び出せば、使えることになります

一見、ゲテモノっぽい印象がありますが、どのユーザー環境にもPerlの処理系は入っていたりするので、どのユーザー環境でも使えるというメリットがあります。サブルーチン化しておくと、再利用性も高くなることでしょう。