Cocoa AppleScript PDFからRTF (ワード検索し、文字色を変更) AppleScript [09]
AppleScript [09] Cocoa AppleScript 第3版 PDFからRTFファイル書き出し (ワード検索し、文字色を変更)
前回の AppleScript に不備があり、PDF から書き出した RTF(RichTextFormat) ファイルをダブルクリックしても「テキストエディット.app」が起動しない問題に対処!( TXTファイル書き出しに問題はありません )
"Skim"アプリで PDF から抽出したRTFデータは 色やフォント(フォントサイズを含む)等の属性情報が無いが 、属性情報の初期セットと変更は成功。
① 初期設定サンプルとして、テキスト全体に「シアン・Osaka等幅 18ポ」を設定
② 追加(変更)サンプルとして、指定キーワードで検索し 文字色を黄色に変更(全箇所)
(注) AppleScript にて、フォルダ選択ダイアログを利用します!
2021/10/30 Ver. 3.4「Cocoa AppleScript 第3版」「PDFからRTF(ワード検索し、文字色を変更)」ハンドラーの追加
→ AppleScript は "filePath.scpt" ファイルの「前回のハンドラーを修正(追加あり)」、VBAコードも「前回のプロシージャの修正(インターフェース)」となります。
念のため書いておきますが、PDF内の文字が画像の一部になったままではテキスト抽出できないため その場合はいわゆる「OCR(光学文字認識)」スキャンを行う必要あり!
テキストエディタ等で閉じるのを忘れた場合(つまり、ファイルをオープンしたままだと)、メモリ不足など Mac が深刻な症状に陥る場合があります。 筆者もテストの際2、3度「やっちまった」次第で、いきなり Safari や Excelが終了してビックリ...
いつも見ている 「ぴよまるソフトウェア」さんのブログ記事(以下リンク)から、今回 2つのハンドラーを拝借 させて頂きました。
指定キーワードで(全箇所)検索 部品が素晴らしい! この部品を見つけなければ、今回は 全箇所ではなく「最初のみの検索」となるところでした。
AppleScriptの最新情報を入手可能なブログ(ただし、中級者以上向け)で、超オススメ!
【AppleScriptの変更(追加もあり)】 AppleScript [09] Cocoa AppleScript 第3版 PDFからRTFファイル書き出し (ワード検索し、文字色を変更)
まず、Excel メニューバーの [ヘルプ] - [更新プログラムのチェック] にて、適用されていないアップデートがあれば行ってください。
VBA の AppleScriptTask コマンド パラメータの注意点
ファイルパスと区別するため フォルダパス指定には 末尾に必ず / を付加 してください! フォルダフルパス なら 先頭と末尾が必ず / となります。
OneDrive や iCloud、Dropbox、Box 等のサービスをブラウザ利用でなくフォルダにリンクしている場合も(リンク)パスが判れば指定可能ですが、https:// が先頭に付いたり ネットワーク経由のアクセスのためとんでも無く時間がかかりますので ローカルPC(Mac本体内の高速なSSDやHDDドライブ)上のフォルダパスを指定してください。
第3版 Cocoa AppleScript コード 前回追加したハンドラーを修正(追加もあり)
RTFファイル書き出し時のみ有効な「検索キーワード」を先頭パラメータとして追加したため、合計4パラメータに! なお、TXTファイル書き出し時 「検索キーワード」は無効ななため、空文字列をセットすると良いでしょう。
前回 追加した AppleScript ハンドラーを修正後、(小さな)3つのハンドラーを追加します!
前回 末尾に追加した1ハンドラーを削除後、以下のAppleScriptコードを 末尾に 順に コピー&ペースト(貼り付け)後、 コンパイル後に ファイルを保存 してください。 VBAコード に関しては「次章」に記載します。 操作方法がよく解らなくても、前回以前の記事を読めば大丈夫です。
--2021/10/20 Ver.3.3 Cocoa AppleScript 対応 第3版 以下ハンドラーを追加
--2021/10/30 Ver.3.4 作成されるRTFファイルに不備があったため修正 (+「文字の色・サイズ・フォント等の属性」を初期セットし、検索キーワードの色を全箇所変更)
-- 指定フォルダ(子フォルダを含む)内のPDFファイルを順に読み込み、テキストファイルを書き出し
-- パラメータ[1] 検索キーワード(rtf指定時のみ有効で、全箇所「黄」に変更)txt時は無視
-- パラメータ[2] プロンプト文字列:""なら、当ハンドラー設定の初期値
-- パラメータ[3] 初期表示フォルダ:""なら、無指定(前回と同じフォルダを開く)
-- パラメータ[4] 書式": txt" or "rtf"(省略時は、"txt":PlainText指定)RTF:RichTextFormat
-- 実行結果[0~n] (発見した)PDFファイルのフルパス、ただし ".pdf" の拡張子は削除済み
--[注意] Macに予め、Skimアプリ(無料のPDFリーダー+注釈付加ツール)のインストールが必要!
on putTextFromPdfOfSelectFolder(paramStr)
set returnText to ""
set promptStr to "PDF格納フォルダを選択!(子フォルダ対応)" -- ファイル選択時のプロンプト文字列
try
set tmp to (AppleScript's text item delimiters) -- 現在のデリミタを保存
set (AppleScript's text item delimiters) to {LF} -- デリミタを変更
set paramList to (every text item of paramStr) -- 分割し、List格納
set (AppleScript's text item delimiters) to tmp -- 保存したデリミタに戻す
set searchWord to ((first item of paramList) as string) --検索キーワード(RTFのみ有効)
set paramList to (rest of paramList) --最初の要素を削除
set str to ((first item of paramList) as string)
if (str ≠ "") then --not=は「/=」と記述
set promptStr to str
end if
set paramList to (rest of paramList) --最初の要素を削除
set defaultLocation to ((first item of paramList) as string)
set paramList to (rest of paramList) --最初の要素を削除
if (count paramList) = 0 then
set isRtf to false
else
set rtf to ((first item of paramList) as string)
if rtf is in {"rtf", "RTF", "Rtf"} then
set isRtf to true --RTF:RichTextFormat
else
set isRtf to false
end if
end if
activate --ウィンドウの前面に表示!
if (defaultLocation = "") then
set theAlias to (choose folder with prompt promptStr)
else
set defaultLocation to (POSIX path of defaultLocation)
set theAlias to (choose folder with prompt promptStr default location defaultLocation)
end if --選択したフォルダの「エイリアス値」が返される
--tell application "Finder"
set theFolder to (POSIX path of (theAlias as string)) -- 指定フォルダのPOSIXパス
--end tell
set fileWriteCount to 0 --ファイル書き込み成功 件数
set theList to {}
set theList to my retFullPathWithinAFolderWithRecursiveFilterByExt(theFolder, "pdf")
tell application "Skim" --AppleScriptで利用し易い PDF読み込み(注釈付加も可能)アプリ
activate --"Skim"App
repeat with fName in theList --PDFファイルのフルパス(ただし、末尾の .pdf は削除)
set theDoc to (open (fName & ".pdf")) --Open PDF's file
tell front document --該当PDFファイルのみを対象!(同時に複数Open可能なため)
if isRtf then --RTF:RichTextFormat
set rtfFromPDF to (get text for as rich text) --該当PDFから、全[default:RTF]を抽出
else
set textFromPDF to (get text for as text) --該当PDFから、全テキストを抽出
end if
end tell --「文字コードと改行コード」は元のPDFファイルのまま、抽出
--[注1] 既に同一ファイルが存在したら、無条件 置き換え
--[注2] テキストファイル(文字コードUTF-8)の改行コードは、PDFファイルのまま
if isRtf then --RTF:RichTextFormat
set theResult to my saveStyledTextAsRTF(rtfFromPDF, (fName & ".rtf"), searchWord) --RTFのみ、検索文字列の色を全箇所変更
else
set theResult to my write_to_text_file_asUTF8(textFromPDF, (fName & ".txt"), false, LF)
end if
if theResult then --ファイル書き込み成功の場合
set fileWriteCount to (fileWriteCount + 1)
end if
close theDoc --Close PDF's file
end repeat
delay 3.0 -- 無くても、ほとんどの場合 動作するはず
quit --"Skim"Appの終了
end tell
activate
display dialog (fileWriteCount as string) & " 件のテキストファイルを書き込みました!" buttons {"10秒で閉じます!"} with icon caution giving up after 10
set returnText to "" -- ハンドラ-の戻り値を初期化
set returnText to getTextWithDelimiters(theList, {LF}) --ハンドラ-の戻り値へ 格納
set the clipboard to {} --クリップボードの初期化
set the clipboard to returnText --読み込みファイルデータを、クリップボードへ格納(UTF-8)
return returnText
on error
set returnText to "" -- ハンドラ-の戻り値を初期化
set (AppleScript's text item delimiters) to tmp -- 保存したデリミタに戻す
display dialog "★ AppleScriptTask (putTextFromPdfOfSelectFolder) ★" & return & return & " キャンセル あるいは 異常終了" buttons {"10秒で閉じます!"} with icon caution giving up after 10
end try -- 「キャンセル」ボタン押下時用のエラーハンドリング
return returnText -- display dialog returnText
end putTextFromPdfOfSelectFolder
--スタイル付きテキストを、指定フルパス(POSIX path)に RTF で書き出し
--[1] RTF属性サンプルとして、テキスト全体に「文字色・フォント+文字サイズ」を初期設定
--[2] 指定キーワードで検索し、黄色に変更(全箇所)
on saveStyledTextAsRTF(rtfFromPDF, theFilePath, searchWord)
try
activate
--(注) フォント(+フォントサイズ)は PostScript名で指定 Sample:Osaka-等幅18ポ
--set theFont to NSFont's fontWithName:"Meiryo" |size|:18 --メイリオ18ポ
set theFont to NSFont's fontWithName:"Osaka-Mono" |size|:18
set theCyanColor to NSColor's cyanColor() --Sample:文字色をシアン設定
set theDict to NSDictionary's dictionaryWithObjects:{theCyanColor, theFont} forKeys:{NSForegroundColorAttributeName, NSFontAttributeName}
set theATS to NSMutableAttributedString's alloc()'s initWithString:rtfFromPDF attributes:theDict --RTF属性の初期設定
--指定キーワードで検索し、黄色に変更(全箇所)--RTF属性の追加
if searchWord ≠ "" then
set theYellowColor to NSColor's yellowColor()
my changeColorOfSearchWord(theATS, rtfFromPDF, searchWord, theYellowColor)
end if
--時間はかかるかもしれないが、別キーワードで検索を繰り返すことも可能!
--set theRedColor to NSColor's redColor()
--my changeColorOfSearchWord(theATS, rtfFromPDF, "AppleScript", theRedColor)
--set theATSsLength to theATS's |string|()'s |length|() -- 's string's Length
set theATSsLength to theATS's |length|() --NSMutableAttributedString's Length
set allRange to current application's NSMakeRange(0, theATSsLength)
-- write the attributed string out as an RTF file(無条件、置換)
--PDFファイルの「文字(改行)コード」のまま、テキストファイル書き込み!
set rtfData to theATS's RTFFromRange:allRange documentAttributes:{DocumentType:current application's NSRTFTextDocumentType} --UTI (public.rtf) 属性を設定
return (rtfData's writeToFile:theFilePath atomically:true) as boolean --UTF-8(LF)想定
on error
display dialog "★ AppleScriptTask (saveStyledTextAsRTF) ★" & return & return & " キャンセル あるいは 異常終了" buttons {"10秒で閉じます!"} with icon caution giving up after 10
return false
end try -- 「キャンセル」ボタン押下時用のエラーハンドリング
end saveStyledTextAsRTF
-- Created 2017-09-19 by Takaaki Naganoya
-- 2017 Piyomaru Software
--指定のAttributedString内で指定文字列が含まれる箇所に指定の色をつける(結果は参照渡し)
on changeColorOfSearchWord(mutableAttrStr, origStr, aTargStr, aColor)
try
set rangeArray to my searchWordWithRange(origStr, aTargStr)
repeat with aRange in rangeArray
(mutableAttrStr's addAttribute:(NSForegroundColorAttributeName) value:aColor range:aRange)
end repeat
on error --Add by Ataruchi 2021/10/30
display dialog "★ AppleScriptTask (changeColorOfSearchWord) ★" & return & return & " キャンセル あるいは 異常終了" buttons {"10秒で閉じます!"} with icon caution giving up after 10
end try
end changeColorOfSearchWord
-- Created 2017-09-19 by Takaaki Naganoya
-- 2017 Piyomaru Software
--指定テキストデータ(aTargText)内に、指定文字列(aSearchStr)が含まれる範囲情報(NSRange)をすべて取得する
on searchWordWithRange(aTargText, aSearchStr)
try
set aStr to NSString's stringWithString:aTargText
set bStr to NSString's stringWithString:aSearchStr
set hitArray to NSMutableArray's alloc()'s init()
set cNum to (aStr's |length|()) as integer
set aRange to current application's NSMakeRange(0, cNum)
repeat
set detectedRange to aStr's rangeOfString:bStr options:NSLiteralSearch range:aRange
set aLoc to (detectedRange's location)
if (detectedRange's |length|() = 0) then exit repeat --Add by Ataruchi 2021/10/30
--CAUTION !!!! Sometimes aLoc returns not NSNotFound (-1) but a Very large number
if (aLoc > 9.999999999E+9) or (aLoc = (current application's NSNotFound)) then exit repeat
hitArray's addObject:detectedRange
set aNum to aLoc as integer
set bNum to (detectedRange's |length|) as integer
set aRange to current application's NSMakeRange(aNum + bNum, cNum - (aNum + bNum))
end repeat
return hitArray
on error --Add by Ataruchi 2021/10/30
display dialog "★ AppleScriptTask (searchWordWithRange) ★" & return & return & " キャンセル あるいは 異常終了" buttons {"10秒で閉じます!"} with icon caution giving up after 10
hitArray's removeAllObjects() --要素を全て削除する
set emptyArray to NSMutableArray's alloc()'s init() --return値を(再)初期化
return emptyArray
end try
end searchWordWithRange
ごめんなさい。 Cocoa用の(クラス)オブジェクト定義が足りなくて、コンパイルエラーになります。
AppleScript先頭部分の property定義を 以下で置き換えてください!
再度 コンパイル後に ファイルを保存 する必要があります。
--NSObjectを最上位とする以下NSオブジェクトは Cocoa 用のオブジェクトで、Objective-C で実装されている!
property |NSURL| : a reference to current application's |NSURL| --SubDir内を除外したDir内の列挙時に利用
property NSFont : a reference to current application's NSFont
property NSColor : a reference to current application's NSColor
property NSString : a reference to current application's NSString --dirPath, fileName, ext 取得可
property NSPredicate : a reference to current application's NSPredicate --正規表現 Esc処理に一部問題あり
property NSDictionary : a reference to current application's NSDictionary
property NSEnumerator : a reference to current application's NSEnumerator --列挙用
property NSFileManager : a reference to current application's NSFileManager --"Finder" の代替Obj
property NSLiteralSearch : a reference to current application's NSLiteralSearch
property NSMutableArray : a reference to current application's NSMutableArray --変更可能な NSArray
property NSMutableString : a reference to current application's NSMutableString --変更可能な NSString
property NSUTF8StringEncoding : a reference to current application's NSUTF8StringEncoding --UTF-8Encode
property NSFontAttributeName : a reference to current application's NSFontAttributeName
property NSMutableAttributedString : a reference to current application's NSMutableAttributedString
property NSForegroundColorAttributeName : a reference to current application's NSForegroundColorAttributeName
property NSDocumentTypeDocumentAttribute : a reference to current application's NSDocumentTypeDocumentAttribute
property NSDirectoryEnumerationSkipsSubdirectoryDescendants : a reference to current application's NSDirectoryEnumerationSkipsSubdirectoryDescendants --Skip SubDir's (File or Dir)
property NSDirectoryEnumerationSkipsPackageDescendants : a reference to current application's NSDirectoryEnumerationSkipsPackageDescendants --Skip Package. ex) "Microsoft Excel.app"
property NSDirectoryEnumerationSkipsHiddenFiles : a reference to current application's NSDirectoryEnumerationSkipsHiddenFiles --Skip HiddenFiles for Enumeration
ちなみに、 ret で始まる AppleScript の ハンドラー (VBAのプロシージャ相当の呼び出し単位)は Cocoa AS で主処理が実装されていますので、カスタマイズは慎重にお願いします。 その他の ハンドラー も Cocoa AS 実装の部分が判るように記述 しています。("NS"で始まるクラスは Cocoaのオブジェクトで、それ以外で何も記述が無ければ、Vannila AS ということ) Vanilla AS 部分はカスタマイズが楽で「 AppleScriptTask コマンド経由の呼び出し」ではなく、「 filePath.scpt をダブルクリック後に起動される スクリプトエディタ.app にて実行」すれば( Vanilla AS 部分の場合 )エラー発生箇所が判ります。 先頭部分の on test() から end test 内にサンプルのデバッグ用コードを記述済みのため、変更後に [command]+[K] でコンパイル、 test() の実行は [command]+[R] で可能です。 動作に問題が無い場合は [command]+[S] にて保存を忘れないように!
VBA だけでなく AppleScript コードも テキスト保存が可能なため、変更前に [command]+[A] にて全選択後 [command]+[C] でコピー してテキストエディタ等に [command]+[V] にてペースト(貼り付け)しておけば元に戻せます。 カスタマイズ作業前に filePath.scpt と AppleScriptTaskコマンドを記述したExcelファイル を ローカルPC上か クラウド領域にバックアップしておくと なお良いでしょう。
【VBA I/F置換】 AppleScript [09] Cocoa AppleScript 第3版 PDFからRTFファイル書き出し (ワード検索し、文字色を変更)
前回追加した VBAコード を削除後、 末尾に 以降のコードをコピー&ペースト(貼り付け)
' 指定フォルダ(子フォルダを含む)内のPDFファイルを順に読み込み、テキストファイルとして書き出し
' [注意] 予め、Skimアプリ(無料のPDFリーダー+注釈付加ツール)のインストールが必要!
Sub WriteTextFileFromPdfOfFolder()
'フォルダ選択ダイアログで指定すると、同一フォルダ内に「拡張子のみ.txtに変更」して書き込み
On Error GoTo myError
'以下は、AppleScriptTaskコマンドのパラメータ説明
' パラメータ[1] 検索キーワード(rtf指定時のみ有効で、キーワード全箇所を黄色に変更)
' パラメータ[2] プロンプト文字列:""なら、AppleScriptハンドラー設定の初期値
' パラメータ[3] 初期表示フォルダ:""なら、無指定(前回と同じフォルダを開く)
' パラメータ[4] 書式": txt" or "rtf"(省略時は、"txt"プレーンテキスト指定)RTF:RichTextFormat
' 実行結果[0〜n] エラー時は""、 正常終了なら(拡張子なし)該当PDFファイルのフルパスを返す
Dim list() As String ' 「フルパス」格納用として「文字列の配列」を定義
Dim scriptResult As String ' AppleScript 実行結果 (文字列)
Dim scriptParam As String ' AppleScript パラメータ(文字列)
'scriptParam = "" & vbLf & "Select PDF's folder!" & vbLf & "/Users/username/Desktop/" '★txt★
scriptParam = "スクリプティング" & vbLf & "" & vbLf & "" & vbLf & "rtf" '★rtf★
scriptResult = AppleScriptTask("filePath.scpt", "putTextFromPdfOfSelectFolder", scriptParam)
If scriptResult <> "" Then '「キャンセル」ボタン押下時や、PDFファイル無し→空文字列
list = Split(scriptResult, vbLf) 'vbLf = Chr(10) 区切り文字に LF を利用
Call DispArray(list) '実行結果(文字列)の表示
Erase list '配列の初期化
Call GetClipBoard 'クリップボードに格納したテキストデータを取得&表示
End If
Exit Sub
myError:
MsgBox "エラー発生!(WriteTextFileFromPdfOfFolder)"
End Sub
RTFファイル書き出し時のみ有効な「検索キーワード」を先頭パラメータとして追加したため、合計4パラメータに! なお、TXTファイル書き出し時 「検索キーワード」は無効ななため、空文字列 "" をセットすると良いでしょう。
パラメータ[4] 省略時も "txt" 指定! それ以外は、前回の呼び出し方法と同じです。
(1) 「 イミディエイト ウィンドウ から call WriteTextFileFromPdfOfFolder() 」等で実行すると まず フォルダ選択ダイアログ が開くため、PDFファイルを格納したフォルダを指定!
(2) 子フォルダ対応で、「 拡張子 pdf 」を持つファイルのフルパス(処理し易いよう、末尾の .pdf は削除)のリストを作成
(3) 「Skimアプリ」で (2) のリストを順に処理。 PDFファイルをオープン後 各PDF内の全テキストを抽出し、(2) のパスの末尾に .txt あるいは .rtf を付加して 同一フォルダ内に テキストファイルとして書き込み。 (同一ファイル名が存在した場合、無条件 ファイルを置き換え)
(4) 正常終了すると、ファイル書き込み件数をダイアログ表示(10秒で自動的に閉じる)
処理結果として、結果文字列 と クリップボード に (2) のリスト(配列)をセット!
rtf を指定して RTF (RichTextFormat) ファイルを書き出す場合、 PlainText に次のような属性情報を付加することが可能。
① 初期設定サンプルとして、テキスト全体に「シアン・Osaka等幅 18ポ」を設定
② 追加(変更)サンプルとして、指定キーワードで検索し 文字色を黄色に変更(全箇所)
今まで作成した AppleScript 部品を組み合わせて、「PDFからテキスト抽出」機能を付加しました! 部品の再利用化が進むと AppleScript および VBA の両方で少ないコードで機能付加が可能となるため、 再利用し易いように部品を設計するのも重要です。 この辺りは経験を重ねるしかないと思いますので、 VBA や AppleScript に限らず プログラムコードをたくさん書けば 誰でもできるようになるはず...
Mac の VBA のみで実現出来ないことをまた見つけたら、 AppleScript で補完したいと思います! macOS12以降で追加される「ショートカット」アプリとの連携方法も理解できたら、記事にまとめないと...
早く macOS12 が安定すると良いですね。(筆者は、それまで macOS11.6.1 を使用) 愛用の Mac mini 2014 でも遅くなりませんように! macOS11 では intelチップ以外のMacで AppleScript が低速なコアのみで実行されるというバグ? があったため、 macOS12 の AppleScript 実行速度にビックリするそうですよ! intelチップのMacでも少々速くなるらしいです。
[command] + [Q] にてまず VBE を終了し、Excel のウィンドウ内を選択して [command] + [Q] にて Excel も終了させます。 「作業中のブック」を保存するか確認のダイアログ画面が表示された場合は 「保存」しましょう。 「自動保存」済みの場合、保存するかどうかの確認ダイアログ画面は表示されません。
AppleScriptTask コマンドの結果文字列(エラーがあれば、空文字 "" を返す)だけでなく、クリップボードにも同じ内容(改行コードは異なる場合あり)をセットしているため、必要があれば「片方のみ」に変更してください。 ただし、「フルパスやファイル名以外」を返す結果文字列の場合 クリップボードにはセットしないため、「イミディエイト ウィンドウ」にて確認してください!
AppleScript から VBA に数百ファイル以上結果が返されるのであれば、大量データが渡されるオーバーヘッドも含め、クリップボードのみの利用(結果文字列は、正常終了/異常終了のみ判れば良い)がオススメです。 また、バックグラウンド処理前提の場合は 相互で クリップボードの中身を消してしまう可能性 があるため、結果文字列のみの利用が良いでしょう。
1日2時間くらい2ヶ月 AppleScript を勉強した成果として、今回の記事を書きました。 Cocoa AppleScript に関しては初心者のため、不具合があればコメント等で教えて頂けると嬉しいです。
最後まで読んでいただき、ありがとうございます。 また、お越しくださいませ。
// アタル
"Skim" はきっとできるはずと思い「 AppleScriptの穴」ブログの筆者 長野谷さんに教えて頂いた結果、特殊な方法で PDFから オリジナル属性(文字色やフォント等)付きのデータを 取得可能な事が判明!
raw RTF データと呼ばれるそうですが、想像以上に RTFデータフォーマット って複雑みたいです。
やはり、筆者が方法を知らないだけで "Skim" は悪くなかったです!
次回の記事は「 raw RTF データ 」を取得後、ワード検索で「文字背景色」を黄色に変更します。
これで、心置き無く「AppleScript PDF編」を終了できます。