Archive for the 'テキスト処理(text)' Category

2017/09/21 迷路をRTFで作成して脱出経路を赤く着色する v2

2D迷路の作成プログラム「MazeFinder」(By Toby Jennings)をフレームワーク化したものを呼び出して、文字ベースの迷路データをRTFに変換して脱出経路情報を着色してデスクトップに書き出すAppleScriptのアップデート版です。

maze1.png

処理結果はとくに前バージョンと変化ありませんし、実行時間については前バージョンよりも0.02秒ほど余計にかかるようになりました。

では、なぜ書き換えたかといえば、各機能の再利用性の向上が目的です。せっかく書いておいても、他の用途に転用するのに手間がかかるようであれば意味がありません。さまざまな用途に転用しやすい構造(サブルーチンとしてまとめているとか)に書いておくことはとても重要です。

再利用したい機能モジュールをよりサブルーチン化するように書き換えました。こうしておくことで、まったく違った用途にもすぐに転用できます。

本Scriptを試す場合には、MazeFinder.frameworkをダウンロードして~/Library/Frameworksフォルダに入れてください。

–> Download Framework Binary

60×60の迷路を作成して着色してファイル書き出しを終了するまでに、開発環境で0.1秒程度かかりました(前バージョンは0.08秒ぐらい)。

AppleScript名:迷路をRTFで作成して脱出経路を赤く着色する v2
– Created 2017-09-19 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use framework “MazeFinder” –https://github.com/tcjennings/MazeFinder
–http://piyocast.com/as/archives/4834

property NSColor : a reference to current application’s NSColor
property Board2D : a reference to current application’s Board2D
property NSUUID : a reference to current application’s NSUUID
property NSDictionary : a reference to current application’s NSDictionary
property NSMutableArray : a reference to current application’s NSMutableArray
property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString
property NSFontAttributeName : a reference to current application’s NSFontAttributeName
property NSFont : a reference to current application’s NSFont
property NSString : a reference to current application’s NSString
property NSDocumentTypeDocumentAttribute : a reference to current application’s NSDocumentTypeDocumentAttribute
property NSForegroundColorAttributeName : a reference to current application’s NSForegroundColorAttributeName
property Pathfinder : a reference to current application’s Pathfinder
property NSLiteralSearch : a reference to current application’s NSLiteralSearch
–NSNotFoundはプロパティに代入しても認識されなかった

set targFontName to “Courier-Bold” –PostScript Name
set targFontSize to 13 –Point

–迷路テキストデータ作成→Attributed Stringに
set aStr to create2DMazeAndSolveIt(30, 30, 1, 1, 28, 28) of me –Plain Text
set anAttr to changeAttrStrsFontAttribute(aStr, targFontName, targFontSize) of me –Attributed String

–迷路データに着色(参照呼び出し, Call by reference)
markCharOfAttributedString(anAttr, aStr, “!”, (NSColor’s redColor())) of me

–結果のRTFをデスクトップ上に書き出す。ファイル名はUUID.rtf
set targFol to current application’s NSHomeDirectory()’s stringByAppendingPathComponent:“Desktop”
set aRes to my saveStyledTextAsRTF(targFol, anAttr)

–指定のAttributed String内で指定文字列が含まれる箇所に指定の色をつける(結果はメイン側に参照渡し)
on markCharOfAttributedString(anAttr, origStr, aTargStr, aColor)
  set rList to searchWordWithRange(origStr, aTargStr) of me
  
repeat with ii in rList
    (anAttr’s addAttribute:(NSForegroundColorAttributeName) value:aColor range:ii)
  end repeat
end markCharOfAttributedString

–指定の文字列をAttributed Stringに変換して任意のフォントを一括指定
on changeAttrStrsFontAttribute(aStr, aFontPSName, aFontSize)
  set tmpAttr to NSMutableAttributedString’s alloc()’s initWithString:aStr
  
set aRange to current application’s NSMakeRange(0, tmpAttr’s |length|())
  
set aVal1 to NSFont’s fontWithName:aFontPSName |size|:aFontSize
  
tmpAttr’s beginEditing()
  
tmpAttr’s addAttribute:(NSFontAttributeName) value:aVal1 range:aRange
  
tmpAttr’s endEditing()
  
return tmpAttr
end changeAttrStrsFontAttribute

–指定テキストデータ(atargText)内に、指定文字列(aSearchStr)が含まれる範囲情報(NSRange)をすべて取得する
on searchWordWithRange(aTargText, aSearchStr)
  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)
  
  
set aCount to 1
  
repeat
    set detectedRange to aStr’s rangeOfString:bStr options:NSLiteralSearch range:aRange
    
if detectedRange’s location is equal to (current application’s NSNotFound) then exit repeat
    
    
hitArray’s addObject:detectedRange
    
    
set aNum to (detectedRange’s location) as integer
    
set bNum to (detectedRange’s |length|) as integer
    
    
set aRange to current application’s NSMakeRange(aNum + bNum, cNum - (aNum + bNum))
    
set aCount to aCount + 1
  end repeat
  
  
return hitArray
end searchWordWithRange

–スタイル付きテキストを指定フォルダ(POSIX path)にRTFで書き出し
on saveStyledTextAsRTF(targFol, aStyledString)
  set bstyledLength to aStyledString’s |string|()’s |length|()
  
set bDict to NSDictionary’s dictionaryWithObject:“NSRTFTextDocumentType” forKey:(NSDocumentTypeDocumentAttribute)
  
set bRTF to aStyledString’s RTFFromRange:(current application’s NSMakeRange(0, bstyledLength)) documentAttributes:bDict
  
  
set theName to (NSUUID’s UUID()’s UUIDString())
  
set thePath to NSString’s stringWithString:targFol
  
set thePath to (thePath’s stringByAppendingPathComponent:theName)’s stringByAppendingPathExtension:“rtf”
  
return (bRTF’s writeToFile:thePath atomically:true) as boolean
end saveStyledTextAsRTF

–2D迷路を作成して脱出経路を検索して文字列で迷路データを出力する
–迷路サイズ x,y スタート座標 x, y ゴール座標 x,y
on create2DMazeAndSolveIt(xMax, yMax, xStart, yStart, xGoal, yGoal)
  set myBoard to Board2D’s alloc()’s init()
  
myBoard’s setupBoardWithRows:xMax WithColumns:yMax –60×60ぐらいが上限。正方形でないとダメ
  
myBoard’s createMazeFromBoard()
  
  
set myPathfinder to Pathfinder’s alloc()’s init()
  
set myPath to myPathfinder’s findPathThroughMaze:myBoard fromX:xStart fromY:yStart toX:xGoal toY:yGoal
  
  
set aCount to myPath’s |count|()
  
if aCount > 0 then
    set aRes to myBoard’s drawPathThroughMaze:myPath
    
return aRes as string
  else
    return false
  end if
end create2DMazeAndSolveIt

★Click Here to Open This Script 

2017/09/20 迷路をRTFで作成して脱出経路を赤く着色する

2D迷路の作成プログラム「MazeFinder」(By Toby Jennings)をフレームワーク化したものを呼び出して、文字ベースの迷路データをRTFに変換して脱出経路情報を着色してデスクトップに書き出すAppleScriptです。

maze1.png

本Scriptを試す場合には、MazeFinder.frameworkをダウンロードして~/Library/Frameworksフォルダに入れてください。

60×60の迷路を作成して着色してファイル書き出しを終了するまでに、開発環境で0.08秒程度かかりました。

–> Download Framework Binary

迷路作成の処理自体についてはとくに意味はありませんが、迷路プログラム自体にもいろいろあるようです。このMazeFinderについてはいろいろ制約条件がきつく(最大サイズ60×60程度、迷路が正方形でないとクラッシュ)、あまり迷路らしい迷路にもなっていません。

「テキストデータをRTFで出力して特定文字に着色する」というあたりの処理がミソでしょうか。

AppleScript名:迷路をRTFに作成して脱出経路を赤く着色する
– Created 2017-09-19 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
use framework “MazeFinder” –https://github.com/tcjennings/MazeFinder
–http://piyocast.com/as/archives/4831

property NSColor : a reference to current application’s NSColor
property Board2D : a reference to current application’s Board2D
property NSUUID : a reference to current application’s NSUUID
property NSDictionary : a reference to current application’s NSDictionary
property NSMutableArray : a reference to current application’s NSMutableArray
property NSMutableAttributedString : a reference to current application’s NSMutableAttributedString
property NSFont : a reference to current application’s NSFont
property NSString : a reference to current application’s NSString
property NSDocumentTypeDocumentAttribute : a reference to current application’s NSDocumentTypeDocumentAttribute
property NSForegroundColorAttributeName : a reference to current application’s NSForegroundColorAttributeName
property Pathfinder : a reference to current application’s Pathfinder
property NSLiteralSearch : a reference to current application’s NSLiteralSearch

set targFontName to “Courier-Bold” –”Courier New”/”Osaka-Mono”

–Create Maze Text
set aStr to create2DMazeAndSolveIt(30, 30, 1, 1, 28, 28) of me
set anAttr to NSMutableAttributedString’s alloc()’s initWithString:aStr

set bList to {“!”} –Mark Target List

–Set Fixed Width Font
set aRange to current application’s NSMakeRange(0, anAttr’s |length|())
set aVal1 to NSFont’s fontWithName:targFontName |size|:13
anAttr’s beginEditing()
anAttr’s addAttribute:(current application’s NSFontAttributeName) value:aVal1 range:aRange
anAttr’s endEditing()

–Change Attribute (red color)
repeat with i in bList
  set rList to searchWordWithRange(aStr, i as string) of me
  
repeat with ii in rList
    (anAttr’s addAttribute:(current application’s NSForegroundColorAttributeName) value:(NSColor’s redColor()) range:ii)
  end repeat
end repeat

–結果のRTFをデスクトップ上に書き出す
set targFol to current application’s NSHomeDirectory()’s stringByAppendingPathComponent:“Desktop”
set aFileName to (NSUUID’s UUID()’s UUIDString() as text)
set aRes to my saveStyledTextAsRTF(aFileName, targFol, anAttr)

on searchWordWithRange(aTargText, aSearchStr)
  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)
  
  
set aCount to 1
  
repeat
    set detectedRange to aStr’s rangeOfString:bStr options:NSLiteralSearch range:aRange
    
if detectedRange’s location is equal to (current application’s NSNotFound) then exit repeat
    
    
hitArray’s addObject:detectedRange
    
    
set aNum to (detectedRange’s location) as integer
    
set bNum to (detectedRange’s |length|) as integer
    
    
set aRange to current application’s NSMakeRange(aNum + bNum, cNum - (aNum + bNum))
    
set aCount to aCount + 1
  end repeat
  
  
return hitArray
end searchWordWithRange

–スタイル付きテキストを指定フォルダ(POSIX path)にRTFで書き出し
on saveStyledTextAsRTF(aFileName, targFol, aStyledString)
  –Convert NSMutableStyledStrings to RTF
  
set bstyledLength to aStyledString’s |string|()’s |length|()
  
set bDict to NSDictionary’s dictionaryWithObject:“NSRTFTextDocumentType” forKey:(NSDocumentTypeDocumentAttribute)
  
set bRTF to aStyledString’s RTFFromRange:(current application’s NSMakeRange(0, bstyledLength)) documentAttributes:bDict
  
  
– Build Path
  
set theName to NSString’s stringWithString:aFileName
  
–set theName to theName’s stringByReplacingOccurrencesOfString:”/” withString:”_”
  
–set theName to theName’s stringByReplacingOccurrencesOfString:”:” withString:”_”
  
set thePath to NSString’s stringWithString:targFol
  
set thePath to (thePath’s stringByAppendingPathComponent:theName)’s stringByAppendingPathExtension:“rtf”
  
  
return (bRTF’s writeToFile:thePath atomically:true) as boolean
end saveStyledTextAsRTF

–2D迷路を作成して脱出経路を検索して文字列で迷路データを出力する
–迷路サイズ x,y スタート座標 x, y ゴール座標 x,y
on create2DMazeAndSolveIt(xMax, yMax, xStart, yStart, xGoal, yGoal)
  set myBoard to Board2D’s alloc()’s init()
  
myBoard’s setupBoardWithRows:xMax WithColumns:yMax –60×60ぐらいが上限。正方形でないとダメ
  
myBoard’s createMazeFromBoard()
  
  
set myPathfinder to Pathfinder’s alloc()’s init()
  
set myPath to myPathfinder’s findPathThroughMaze:myBoard fromX:xStart fromY:yStart toX:xGoal toY:yGoal
  
  
set aCount to myPath’s |count|()
  
if aCount > 0 then
    set aRes to myBoard’s drawPathThroughMaze:myPath
    
return aRes as string
  else
    return false
  end if
end create2DMazeAndSolveIt

★Click Here to Open This Script 

2017/09/19 DDFileReaderによる行単位でのテキストファイル読み込みテスト

オープンソースのテキスト行単位読み込みプログラム「DDFileReader」(By Dave DeLong)をCocoa Frameworkにした「DDFileReader.framework」を呼び出してテキストの行単位読み込みを行うAppleScriptです。

AppleScriptの標準命令readコマンドでも行単位読み込みができるので(掲載サンプル参照)、本フレームワークを使う必要はほとんどないのですが、とりあえず実験してみました。

本Scriptを試す場合には、DDFileReader.frameworkをダウンロードして~/Library/Frameworksフォルダに入れてください。オリジナルのままだとARC環境下でビルドできなかったので、releaseとかretainとかdeallocとかそのあたりのキーワードが含まれる行をコメントアウトして試していますが、その程度なので問題があったらぜひ知らせてください。自分でもコレを重要なプログラムで使うつもりはないのですが、問題があったら困ります。

–> Download Framework Binary

AppleScript名:DDFileReaderによる行単位でのテキストファイル読み込みテスト
– Created 2017-09-15 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “DDFileReader” –https://stackoverflow.com/questions/1044334/objective-c-reading-a-file-line-by-line
–http://piyocast.com/as/archives/4829

property DDFileReader : a reference to current application’s DDFileReader
property NSMutableArray : a reference to current application’s NSMutableArray

set tmpPath to POSIX path of (choose file)
set aReader to DDFileReader’s alloc()’s initWithFilePath:tmpPath

set tArray to NSMutableArray’s new()
repeat
  set aLine to aReader’s readLine()
  
if aLine = missing value then exit repeat
  
tArray’s addObject:aLine
end repeat

return length of (tArray as list)

★Click Here to Open This Script 

AppleScript名:Pure ASによるファイルの行単位読み込み
set aFile to choose file of type {“public.plain-text”}

set newList to {}

tell current application
  set fH to open for access aFile without write permission
  
try
    repeat
      set fRes to read fH as «class utf8» until return
      
log fRes
      
set the end of newList to fRes
    end repeat
  on error
    close access fH
  end try
  
end tell

return (length of newList)

★Click Here to Open This Script 

2017/09/10 HTMLをplain textに変換 v2

HTMLのファイルをplain textに変換するAppleScriptです。

前バージョンでは、,△蕕じめHTMLを変数に読み込んでおいて、△修離如璽燭NSString経由でNSAttributedStringに(文字エンコーディングを指定しつつ)読み込んでいました。

このため、,涼奮で文字コードエンコーディングの判定がうまく行かないと、正しいPlain Textが得られないという「弱点」がありました(一応、対策のためにテキストエディタ並みの日本語テキストエンコーディング自動判定ライブラリを併用していますが、毎回掲載するわけにもいきません)。

これに対してShane Stanleyから「こうしたほうが」というツッコミがあったのが本Scriptです。HTMLファイルのファイルパスをNSDataに渡して読み込む、▲如璽燭NSAttaributedStringに読み込む という処理に変更。これまで読み込みに問題のあった(テキストエンコーディング判定失敗)HTMLも正しく読み込めているようです。

AppleScript名:HTMLをplain textに変換 v2
– Created 2017-09-09 by Takaaki Naganoya
– Created 2017-09-09 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4821

property NSAttributedString : a reference to current application’s NSAttributedString
property NSData : a reference to current application’s NSData

set aFile to choose file
set aPlainText to HTMLDecode(POSIX path of aFile) of me

on HTMLDecode(thePath)
  set theData to NSData’s dataWithContentsOfFile:thePath
  
set attStr to NSAttributedString’s alloc()’s initWithHTML:theData documentAttributes:(missing value)
  
return (attStr’s |string|()) as string
end HTMLDecode

★Click Here to Open This Script 

2017/09/08 HTMLをplain textに変換(UTF8)

HTMLのファイルをplain textに変換するAppleScriptです。

HTMLのplain text化とくに日本語テキスト入りのHTMLのPlain Text化は昔(Mac OS X 10.4ぐらいの時代)はひじょうに厄介な処理でした。それが、Cocoaの機能を使うと割と簡単にできるようになり、AppleScriptによる処理の「死角」が少なくなってきた印象があります。

HTMLのテキストエンコーディングについては、ヘッダーに書かれている文字コードと実際の文字コードが異なる可能性もあるため(ありがち)、いまのところテキストのエンコーディング自動判別ルーチンを併用しています。ただ、もうちょっと簡潔に処理できないかとも思うところです(2パスでHTMLを読み込んで、1パス目ではヘッダー部分のみ読み取ってエンコーディング情報を取得、2パス目で取得したエンコーディング情報に基づいて全体を読み直し、とか?)。

本ルーチンは、掲載のためにとりあえずHTMLがUTF-8で書かれているという前提にもとづいて処理を行なっています。

→ 改修版(v2)はこちら

AppleScript名:HTMLをplain textに変換(UTF8)
– Created 2017-09-08 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “AppKit”
–http://piyocast.com/as/archives/4818

property NSUnicodeStringEncoding : a reference to current application’s NSUnicodeStringEncoding
property NSAttributedString : a reference to current application’s NSAttributedString
property NSString : a reference to current application’s NSString

set aFile to choose file
set aRes to read aFile as «class utf8» –文字エンコーディング自動判定処理を行なったほうがよい
set aPlainText to HTMLDecode(aRes) of me

on HTMLDecode(HTMLString)
  set theString to NSString’s stringWithString:HTMLString
  
set theData to theString’s dataUsingEncoding:(NSUnicodeStringEncoding)
  
set attStr to NSAttributedString’s alloc()’s initWithHTML:theData documentAttributes:(missing value)
  
return (attStr’s |string|()) as string
end HTMLDecode

★Click Here to Open This Script 

2017/09/06 文字種類変換(ASOC)

macOS 10.12以降で利用できるNSStringの文字種類変換機能を呼び出して手軽に文字種類変換を行うAppleScriptです。

Shane Stanleyから「macOS 10.12以降なら全角半角文字変換が簡単にできるよー」と、メールで教えてもらいました。

実際にAppleのWeb Referenceで確認してみたら、半角⇄全角変換以外にもいろいろ変換機能が用意されているのを見つけたので、まとめておきました。

ここに挙げた以外にもいろいろ文字種類変換メソッドはあるのですが、日本語に関係するもののみピックアップしました。

AppleScript名:文字種類変換(ASOC)
– Created 2017-09-06 by Shane Stanley
– Modified 2017-09-06 by Takaaki Naganoya
use AppleScript version “2.5″ – (10.12) or later
use framework “Foundation”
use scripting additions
–http://piyocast.com/as/archives/4811

property NSString : a reference to current application’s NSString
property NSStringTransformFullwidthToHalfwidth : a reference to current application’s NSStringTransformFullwidthToHalfwidth
property NSStringTransformHiraganaToKatakana : a reference to current application’s NSStringTransformHiraganaToKatakana
property NSStringTransformLatinToHiragana : a reference to current application’s NSStringTransformLatinToHiragana
property NSStringTransformLatinToKatakana : a reference to current application’s NSStringTransformLatinToKatakana
property NSStringTransformToUnicodeName : a reference to current application’s NSStringTransformToUnicodeName
property NSStringTransformToXMLHex : a reference to current application’s NSStringTransformToXMLHex

set a01 to hanToZen(“トウキョウト”) of me
–>  ”トウキョウト”–Zenkaku (Full Width)

set a02 to zenToHan(a01) of me
–>  ”トウキョウト” –Hankaku (Half Width)

set a03 to katakanaToHiraganaTo(a01) of me
–>  ”とうきょうと”

set a04 to hiraganaToKatakana(a03) of me
–>  ”トウキョウト”

set a05 to hiraganaToalphabet(a03) of me
–>  ”toukyouto”

set a06 to alphabetToHiragana(a05) of me
–>  ”とうきょうと”

set a07 to katakanaToAlphabet(a04) of me
–>  ”toukyouto”

set a08 to alphabetToKatakana(a07) of me
–>  ”トウキョウト”

set a09 to characterToUnicodeName(“あ”) of me
–>  ”\\N{HIRAGANA LETTER A}”

set a10 to unicodeNameToCharacter(a09) of me
–>  ”あ”

set a11 to stringToXMLHex(“あ”) of me
–>  ”あ”

set a12 to xmlHexTostring(a11) of me
–>  ”あ”

–半角→全角変換
on hanToZen(aStr)
  set aString to NSString’s stringWithString:aStr
  
return (aString’s stringByApplyingTransform:(NSStringTransformFullwidthToHalfwidth) |reverse|:true) as string
end hanToZen

–全角→半角変換
on zenToHan(aStr)
  set aString to NSString’s stringWithString:aStr
  
return (aString’s stringByApplyingTransform:(NSStringTransformFullwidthToHalfwidth) |reverse|:false) as string
end zenToHan

–ひらがな→カタカナ変換
on hiraganaToKatakana(aStr)
  set aString to NSString’s stringWithString:aStr
  
return (aString’s stringByApplyingTransform:(NSStringTransformHiraganaToKatakana) |reverse|:false) as string
end hiraganaToKatakana

–カタカナ→ひらがな変換
on katakanaToHiraganaTo(aStr)
  set aString to NSString’s stringWithString:aStr
  
return (aString’s stringByApplyingTransform:(NSStringTransformHiraganaToKatakana) |reverse|:true) as string
end katakanaToHiraganaTo

–ローマ字→ひらがな変換
on alphabetToHiragana(aStr)
  set aString to NSString’s stringWithString:aStr
  
return (aString’s stringByApplyingTransform:(NSStringTransformLatinToHiragana) |reverse|:false) as string
end alphabetToHiragana

–ひらがな→ローマ字変換
on hiraganaToalphabet(aStr)
  set aString to NSString’s stringWithString:aStr
  
return (aString’s stringByApplyingTransform:(NSStringTransformLatinToHiragana) |reverse|:true) as string
end hiraganaToalphabet

–ローマ字→カタカナ変換
on alphabetToKatakana(aStr)
  set aString to NSString’s stringWithString:aStr
  
return (aString’s stringByApplyingTransform:(NSStringTransformLatinToKatakana) |reverse|:false) as string
end alphabetToKatakana

–カタカナ→ローマ字変換
on katakanaToAlphabet(aStr)
  set aString to NSString’s stringWithString:aStr
  
return (aString’s stringByApplyingTransform:(NSStringTransformLatinToKatakana) |reverse|:true) as string
end katakanaToAlphabet

–文字→Unicode Name変換
on characterToUnicodeName(aStr)
  set aString to NSString’s stringWithString:aStr
  
return (aString’s stringByApplyingTransform:(NSStringTransformToUnicodeName) |reverse|:false) as string
end characterToUnicodeName

–Unicode Name→文字変換
on unicodeNameToCharacter(aStr)
  set aString to NSString’s stringWithString:aStr
  
return (aString’s stringByApplyingTransform:(NSStringTransformToUnicodeName) |reverse|:true) as string
end unicodeNameToCharacter

–文字→XML Hex変換
on stringToXMLHex(aStr)
  set aString to NSString’s stringWithString:aStr
  
return (aString’s stringByApplyingTransform:(NSStringTransformToXMLHex) |reverse|:false) as string
end stringToXMLHex

–XML Hex→文字変換
on xmlHexTostring(aStr)
  set aString to NSString’s stringWithString:aStr
  
return (aString’s stringByApplyingTransform:(NSStringTransformToXMLHex) |reverse|:true) as string
end xmlHexTostring

★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/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 

2017/08/31 指定フォルダ内の指定文字列を名称に含むファイルを抽出する v3

指定フォルダ内に存在するファイルを名称の一部に含むキーワードで抽出するAppleScriptです。NSFileManager経由で高速に処理します。結果はPOSIX pathのlistで返します。

前バージョンのScriptに対してShane Stanleyからツッコミが入って、

(1)as «class furl»で結果を返す(fileのlistとして結果が返る)処理はmacOS 10.10.xでは結果が正しくBridgeされないので、もう少し丁寧に処理するか10.10を対象外に

(2)ループ処理について、AppleScriptで処理可能な常識的な範囲のアイテム数(高速化処理をしていなければ数千程度)の範囲であればrepeat with in のループ処理の方がobjectEnumeratorでループするより速い(数十万〜数百万アイテムの場合にはobjectEnumerator)

(3)Package File(実体はフォルダ)についても区別する/しない処理を行なったほうがいい

ということで修正してみました。指定フォルダから、指定文字列を名称に含むファイルを抽出する処理は、登場頻度もたいへん高いので、Finder経由ではなくこうしたCocoaの機能を利用するルーチンにさしかえると大幅な処理速度向上が見込まれます。とくに、非力なマシンでの速度向上が期待されます。

個人的には、macOS 10.10.xはScripting Bridge系のバグやFolder Actionのバグなど問題が多かったので、なるべく対象外にしたい気持ちでいっぱいです。

AppleScript名:指定フォルダ内の指定文字列を名称に含むファイルを抽出する v3
– Created 2017-08-28 by Takaaki Naganoya
– Modified 2017-08-29 by Shane Stanley
– Modified 2017-08-30 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4795

property NSURLIsDirectoryKey : a reference to current application’s NSURLIsDirectoryKey
property NSDirectoryEnumerationSkipsHiddenFiles : a reference to current application’s NSDirectoryEnumerationSkipsHiddenFiles
property NSPredicate : a reference to current application’s NSPredicate
property NSMutableArray : a reference to current application’s NSMutableArray
property NSDirectoryEnumerationSkipsPackageDescendants : a reference to current application’s NSDirectoryEnumerationSkipsPackageDescendants
property NSFileManager : a reference to current application’s NSFileManager
property |NSURL| : a reference to current application’s |NSURL|
property NSDirectoryEnumerationSkipsSubdirectoryDescendants : a reference to current application’s NSDirectoryEnumerationSkipsSubdirectoryDescendants

set sourceFolder to POSIX path of (choose folder)
set aKeyword to “スクリーン”

set file1aRes to my filterOutFilesByName:aKeyword fromDirectory:sourceFolder exceptPackages:false
set file1bRes to my filterOutFilesByNameFromDirectory(aKeyword, sourceFolder, false)
–>  {”/Users/me/Desktop/スクリーンショット 2017-04-15 13.18.03.png”, ….”/Users/me/Desktop/スクリーンテスト.scptd”}

set file2Res to my filterOutFilesByName:aKeyword fromDirectory:sourceFolder exceptPackages:true
set file2bRes to my filterOutFilesByNameFromDirectory(aKeyword, sourceFolder, true)
–>  {”/Users/me/Desktop/スクリーンショット 2017-04-15 13.18.03.png”, ….}

–Get files

–指定フォルダ内の指定文字列を含むファイル名のファイルをPOSIX pathのlistで抽出する
on filterOutFilesByName:(fileNameStr as string) fromDirectory:(sourceFolder) exceptPackages:(packageF as boolean)
  set fileManager to NSFileManager’s defaultManager()
  
set aURL to |NSURL|’s fileURLWithPath:sourceFolder
  
set theOptions to ((NSDirectoryEnumerationSkipsPackageDescendants) as integer) + ((NSDirectoryEnumerationSkipsHiddenFiles) as integer) + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) as integer)
  
set directoryContents to fileManager’s contentsOfDirectoryAtURL:aURL includingPropertiesForKeys:{} options:theOptions |error|:(missing value)
  
set findPredicates to NSPredicate’s predicateWithFormat_(“lastPathComponent CONTAINS %@”, fileNameStr)
  
set foundItemList to directoryContents’s filteredArrayUsingPredicate:findPredicates
  
  
–Remove Folders From found URL Array
  
set anArray to NSMutableArray’s alloc()’s init()
  
repeat with i in foundItemList
    set j to contents of i
    
set {theResult, isDirectory} to (j’s getResourceValue:(reference) forKey:(NSURLIsDirectoryKey) |error|:(missing value))
    
    
–Collect files
    
if (isDirectory as boolean = false) then
      (anArray’s addObject:j)
      
    else if (packageF = false) then
      –Allow Package files?
      
set {theResult, isPackage} to (j’s getResourceValue:(reference) forKey:(current application’s NSURLIsPackageKey) |error|:(missing value))
      
if (isPackage as boolean) = true then
        (anArray’s addObject:j)
      end if
    end if
    
  end repeat
  
  
return (anArray’s valueForKey:“path”) as list
end filterOutFilesByName:fromDirectory:exceptPackages:

on filterOutFilesByNameFromDirectory(fileNameStr as string, fromDir, packageF as boolean)
  return my filterOutFilesByName:(fileNameStr as string) fromDirectory:fromDir exceptPackages:(packageF as boolean)
end filterOutFilesByNameFromDirectory

★Click Here to Open This Script 

2017/08/27 数値文字列に対して、少数点以下の数値の切り上げ、切り下げ

数値文字列(numeric string)に対して、少数点以下の数値の切り上げ、切り下げを行うAppleScriptです。

REST APIで桁数の多い数値文字列を返してくるものがあるので、その変換や桁数指定のために作成したものです。指定桁での数値の切り上げ、切り下げを処理してから数値に変換しないと指数表示になっていまひとつ取り回しが良くなかったので、数値文字列の状態で切り上げ、切り下げを行うようにしてみました。

AppleScript名:数値文字列に対して、少数点以下の数値の切り上げ、切り下げ
– Created 2017-08-12 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4791

property NSString : a reference to current application’s NSString
property NSNumber : a reference to current application’s NSNumber
property NSNumberFormatter : a reference to current application’s NSNumberFormatter
property NSNumberFormatterRoundUp : a reference to current application’s NSNumberFormatterRoundUp
property NSNumberFormatterRoundDown : a reference to current application’s NSNumberFormatterRoundDown

set a to “0.9096617698669434″
set a1Res to roundingDownNumStr(a, 2) of me
–>  0.9

set b to “0.0001830748806241899″
set b1Res to roundingDownNumStr(b, 2) of me
–>  0.0

on roundingDownNumStr(aNum as string, aDigit as integer)
  set a to NSString’s stringWithString:aNum
  
set aa to a’s floatValue()
  
set aFormatter to NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setMaximumFractionDigits:aDigit
  
aFormatter’s setRoundingMode:(NSNumberFormatterRoundDown)
  
set aStr to aFormatter’s stringFromNumber:(NSNumber’s numberWithFloat:aa)
  
return (aStr as text) as real
end roundingDownNumStr

on roundingUpNumStr(aNum as string, aDigit as integer)
  set a to NSString’s stringWithString:aNum
  
set aa to a’s floatValue()
  
set aFormatter to NSNumberFormatter’s alloc()’s init()
  
aFormatter’s setMaximumFractionDigits:aDigit
  
aFormatter’s setRoundingMode:(NSNumberFormatterRoundUp)
  
set aStr to aFormatter’s stringFromNumber:(NSNumber’s numberWithFloat:aa)
  
return (aStr as text) as real
end roundingUpNumStr

★Click Here to Open This Script 

2017/08/23 指定フォルダ以下のLocalのHTMLのテキストエンコーディングをUTF-8に書き換えて保存

Localの指定フォルダ以下にあるHTMLをすべてSpotlightで抽出し、見つかったすべてのHTMLのテキストエンコーディングをUTF-8に変更して上書き保存するAppleScriptです。

本Script単体では動作確認できないので、確認のためにアプレットを用意しておきました。HTMLへの要素アクセスにはオープンソースのHTMLReader.framework(By Nolan Waite)を、Spotlight検索にはShane Stanleyの「Metadata Lib」を、日本語テキストファイルのエンコーディング自動検出には自作の「japaneseTextEncodingDetector」を用いています。これらをすべて含んだアプレット「htmlutf8rewriter.app」を用意しておきました(Code Signずみ)。

–> Download htmlutf8rewriter.zip

実際にテストデータ(7,979 files)で実験してみたところ、MacBook Air 2011(Core i5, 1.6GHz)上で702 Seconds、12分で処理できました(macOS 10.13beta 7で実行)。

バスが遅くて、非力なUプロセッサで、放熱機構が弱い、鈍足なMacBook Air 2011でこの程度の速度なので、手元のMacBook Pro 2012で実行すると半分ぐらいの時間で実行できるものと思われます。

Dual Core(4 thread)のMacBook Airでも1〜2thread分は処理に余裕があったので、よりコア数の多いMac上で実行する場合には並列実行するとマシンパワーを絞り出せるものと思われます。

用意したテストデータには行儀の悪いものも混在していたので、300程度のエラーが発生。こうしたエラーデータに対しては、別途何らかのテキストエディタをコントロールして書き換え&保存を行なってもよいのかもしれません。

最初からテキストエディタをコントロールして書き換えを行うと並列処理できないですし、AppleScript単体で実行するよりも処理時間がかかります(現状だと秒間11ファイルぐらい処理できていますが、外部のテキストエディタを制御して処理すると秒間2ファイルぐらいまで落ちるはずです)。

AppleScript名:指定フォルダ以下のLocalのHTMLのテキストエンコーディングをUTF-8に書き換えて保存
– Created 2017-08-23 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “HTMLReader” –https://github.com/nolanw/HTMLReader
use jLib : script “japaneseTextEncodingDetector”
use mdLib : script “Metadata Lib” version “1.0.0″
–http://piyocast.com/as/archives/4789

property NSString : a reference to current application’s NSString
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property HTMLDocument : a reference to current application’s HTMLDocument

set theFolder to choose folder
set theRecord to mdLib’s searchFolders:{theFolder} searchString:“kMDItemFSName ENDSWITH[c] ’.html’” searchArgs:{}
set aCount to 1

repeat with i in theRecord
  set hRes to overWriteHTMLWithUTF8Encoding(i) of me
  
if hRes = true then
    set aCount to aCount + 1
  end if
end repeat

return aCount

on overWriteHTMLWithUTF8Encoding(aPOSIX)
  set aRes to readJapanesTextFileWithGuessingEncoding(aPOSIX) of jLib
  
set bRes to retSpecifiedElementsFromHTMLString(aRes, “meta”) of me
  
–>  {(HTMLElement) 0 children>, (HTMLElement) 0 children>, ….
  
  
–Search Element
  
set aCount to 0
  
set hitF to false
  
repeat with i in bRes
    set tRes to i’s attributes()
    
–> (NSDictionary) {http-equiv:”Content-Type”, content:”text/html; charset=euc-jp”}
    
    
set kList to tRes’s allKeys() as list
    
–> {”http-equiv”, “content”}
    
    
if kList contains “http-equiv” then
      set hitF to true
      
exit repeat
    end if
    
    
set aCount to aCount + 1
  end repeat
  
  
if hitF = false then return false –Element Not Found
  
  
–Text Encoding を書き換えたHTMLElementを作成する
  
set aHTML to HTMLDocument’s documentWithString:aRes
  
set anElement to (aHTML’s nodesMatchingSelector:“meta”)’s objectAtIndex:aCount
  
anElement’s setObject:“text/html; charset=UTF-8″ forKeyedSubscript:“content”
  
set aRootHTMLStr to anElement’s |document|()’s serializedFragment()
  
  
–Overwrite HTML
  
set aRes to (aRootHTMLStr’s writeToFile:aPOSIX atomically:false encoding:(NSUTF8StringEncoding) |error|:(missing value)) as boolean
  
return aRes
end overWriteHTMLWithUTF8Encoding

–与えられたHTML文字列のうち、指定されたタグ要素で囲まれた文字要素を返す
on retSpecifiedElementsFromHTMLString(anNSString, aTag)
  set aHTML to HTMLDocument’s documentWithString:anNSString
  
set anElement to (aHTML’s nodesMatchingSelector:aTag)
  
return anElement as list
end retSpecifiedElementsFromHTMLString

–与えられたHTML文字列のうち、指定されたタグ要素で囲まれた文字要素を返す
on retSpecifiedElementStringFromHTMLString(anNSString, aTag)
  set aHTML to HTMLDocument’s documentWithString:anNSString
  
set anElement to (aHTML’s nodesMatchingSelector:aTag)’s firstObject()’s textContent()
  
return anElement as string
end retSpecifiedElementStringFromHTMLString

–指定されたローカルのHTMLファイルを、指定文字エンコーディングで読み込んで、指定されたタグ要素で囲まれた文字要素を返す
on retSpecifiedElementStringFromLocalHTML(aPOSIX, anEncoding, aTag)
  set aPath to NSString’s stringWithString:aPOSIX
  
set aData to NSString’s stringWithContentsOfFile:aPath encoding:(anEncoding) |error|:(missing value)
  
if aData = missing value then return false
  
  
set aHTML to HTMLDocument’s documentWithString:aData
  
set anElement to (aHTML’s nodesMatchingSelector:aTag)’s firstObject()’s textContent()
  
return anElement as string
end retSpecifiedElementStringFromLocalHTML

★Click Here to Open This Script 

2017/08/17 japaneseTokenizeのじっけん

macOSの日本語形態素解析の機能を呼び出して日本語テキストをParseするCocoa Framework「japaneseTokenize.framework」を作成しました。それを呼び出すAppleScriptです。

「japaneseTokenize.framework」はゼロから書いたはじめてのObjective-Cのプログラムでもあります(ほぼコピペで作成してありますが)。Header Fileなんて書かないので、見よう見真似&XcodeのWarning Messageのいいなりで試行錯誤していました。

CFStringTokenizer経由で形態素解析してふりがな、元テキスト、ローマ字ふりがなを取得する「parseString:」と、NSLinguisticTagger経由で形態素解析して元テキスト、品詞を取得する「parseStringWithLinguisticTag:」の2つのメソッドを用意してみました。

これら2つの形態素解析機能のparseの結果が矛盾しているかどうかは未確認です(短い文章では同じことを確認してありますが、長い文章でも同じかどうかは未確認。多分同じだとは思いますけれども)。

(CFStringTokenizer)parseString:
(*これ, ら, 2, つ, の, 形態, 素, 解析, 機能, の, parse, の, 結果, が, 矛盾, し, て, いる, か, どう, か, は, 未, 確認, です, 。, 短い, 文章, で, は, 同じ, こと, を, 確認, し, て, あり, ます, が, 、, 長い, 文章, で, も, 同じ, か, どう, か, は, 未, 確認, 。, 多分, 同じ, だ, と, は, 思い, ます, けれど, も, 。*)

(NSLinguisticTagger) parseStringWithLinguisticTag:
(*これら, 2つ, の, 形態素, 解析, 機能, の, parse, の, 結果, が, 矛盾, し, て, いる, か, どう, か, は, 未, 確認, です, 。, 短い, 文章, で, は, 同じ, こと, を, 確認, し, て, あり, ます, が, 、, 長い, 文章, で, も, 同じ, か, どう, か, は, 未, 確認, 。, 多分, 同じ, だ, と, は, 思い, ます, けれど, も, 。*)

(ご参考) AppleScriptの「words of」の実行結果
(*これ, ら, 2, つ, の, 形態, 素, 解析, 機能, の, parse, の, 結果, が, 矛盾, し, て, いる, か, どう, か, は, 未, 確認, です, 短い, 文章, で, は, 同じ, こと, を, 確認, し, て, あり, ます, が, 長い, 文章, で, も, 同じ, か, どう, か, は, 未, 確認, 多分, 同じ, だ, と, は, 思い, ます, けれど, も*)

# うわ、まさかの「微妙に形態素解析結果が違っている」パターンが来た、、、(ーー; macOS 10.13 Betaで試してみたところ、CFStringTokenizer側の形態素解析結果に合わせてNSLinguisticTagger側の出力結果を修正している模様

NSLinguisticTaggerが返す品詞については、いまひとつしっくりこないですし、ユーザーが定義したユーザー定義辞書を考慮して形態素解析したりしないので、実用性についてはさっぱりな印象です。さまざまなWeb APIをAppleScriptから呼び出してみた印象からいえば、Apitoreの形態素解析APIが一番納得できる内容に見えます(Googleの形態素解析APIはまだ呼んでいません)。

jparsers.png

Yahoo!日本語形態素解析Web APIによる解析結果:
–> {ResultSet:{attributes:{xmlns:”urn:yahoo:jp:jlp”, |xsi:schemalocation|:”urn:yahoo:jp:jlp https://jlp.yahooapis.jp/MAService/V1/parseResponse.xsd”, |xmlns:xsi|:”http://www.w3.org/2001/XMLSchema-instance”}, ma_result:{total_count:{|contents|:”8″}, filtered_count:{|contents|:”8″}, word_list:{|word|:{{surface:{|contents|:”来週”}, reading:{|contents|:”らいしゅう”}, pos:{|contents|:”名詞”}}, {surface:{|contents|:”の”}, reading:{|contents|:”の”}, pos:{|contents|:”助詞”}}, {surface:{|contents|:”水曜日”}, reading:{|contents|:”すいようび”}, pos:{|contents|:”名詞”}}, {surface:{|contents|:”に”}, reading:{|contents|:”に”}, pos:{|contents|:”助詞”}}, {surface:{|contents|:”会議”}, reading:{|contents|:”かいぎ”}, pos:{|contents|:”名詞”}}, {surface:{|contents|:”を”}, reading:{|contents|:”を”}, pos:{|contents|:”助詞”}}, {surface:{|contents|:”予約”}, reading:{|contents|:”よやく”}, pos:{|contents|:”名詞”}}, {surface:{|contents|:”。”}, reading:{|contents|:”。”}, pos:{|contents|:”特殊”}}}}}}}

Apitore 日本語形態素解析【新語対応】 ipadic neologdによる解析結果:
–> {startTime:”1502971949537″, tokens:{{partOfSpeechLevel1:”名詞”, baseForm:”来週”, pronunciation:”ライシュー”, position:0, partOfSpeechLevel3:”*”, reading:”ライシュウ”, surface:”来週”, known:true, allFeatures:”名詞,副詞可能,*,*,*,*,来週,ライシュウ,ライシュー”, conjugationType:”*”, partOfSpeechLevel2:”副詞可能”, conjugationForm:”*”, allFeaturesArray:{”名詞”, “副詞可能”, “*”, “*”, “*”, “*”, “来週”, “ライシュウ”, “ライシュー”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”助詞”, baseForm:”の”, pronunciation:”ノ”, position:2, partOfSpeechLevel3:”*”, reading:”ノ”, surface:”の”, known:true, allFeatures:”助詞,連体化,*,*,*,*,の,ノ,ノ”, conjugationType:”*”, partOfSpeechLevel2:”連体化”, conjugationForm:”*”, allFeaturesArray:{”助詞”, “連体化”, “*”, “*”, “*”, “*”, “の”, “ノ”, “ノ”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”名詞”, baseForm:”水曜日”, pronunciation:”スイヨービ”, position:3, partOfSpeechLevel3:”*”, reading:”スイヨウビ”, surface:”水曜日”, known:true, allFeatures:”名詞,副詞可能,*,*,*,*,水曜日,スイヨウビ,スイヨービ”, conjugationType:”*”, partOfSpeechLevel2:”副詞可能”, conjugationForm:”*”, allFeaturesArray:{”名詞”, “副詞可能”, “*”, “*”, “*”, “*”, “水曜日”, “スイヨウビ”, “スイヨービ”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”助詞”, baseForm:”に”, pronunciation:”ニ”, position:6, partOfSpeechLevel3:”一般”, reading:”ニ”, surface:”に”, known:true, allFeatures:”助詞,格助詞,一般,*,*,*,に,ニ,ニ”, conjugationType:”*”, partOfSpeechLevel2:”格助詞”, conjugationForm:”*”, allFeaturesArray:{”助詞”, “格助詞”, “一般”, “*”, “*”, “*”, “に”, “ニ”, “ニ”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”名詞”, baseForm:”会議”, pronunciation:”カイギ”, position:7, partOfSpeechLevel3:”*”, reading:”カイギ”, surface:”会議”, known:true, allFeatures:”名詞,サ変接続,*,*,*,*,会議,カイギ,カイギ”, conjugationType:”*”, partOfSpeechLevel2:”サ変接続”, conjugationForm:”*”, allFeaturesArray:{”名詞”, “サ変接続”, “*”, “*”, “*”, “*”, “会議”, “カイギ”, “カイギ”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”助詞”, baseForm:”を”, pronunciation:”ヲ”, position:9, partOfSpeechLevel3:”一般”, reading:”ヲ”, surface:”を”, known:true, allFeatures:”助詞,格助詞,一般,*,*,*,を,ヲ,ヲ”, conjugationType:”*”, partOfSpeechLevel2:”格助詞”, conjugationForm:”*”, allFeaturesArray:{”助詞”, “格助詞”, “一般”, “*”, “*”, “*”, “を”, “ヲ”, “ヲ”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”名詞”, baseForm:”予約”, pronunciation:”ヨヤク”, position:10, partOfSpeechLevel3:”*”, reading:”ヨヤク”, surface:”予約”, known:true, allFeatures:”名詞,サ変接続,*,*,*,*,予約,ヨヤク,ヨヤク”, conjugationType:”*”, partOfSpeechLevel2:”サ変接続”, conjugationForm:”*”, allFeaturesArray:{”名詞”, “サ変接続”, “*”, “*”, “*”, “*”, “予約”, “ヨヤク”, “ヨヤク”}, partOfSpeechLevel4:”*”}, {partOfSpeechLevel1:”記号”, baseForm:”。”, pronunciation:”。”, position:12, partOfSpeechLevel3:”*”, reading:”。”, surface:”。”, known:true, allFeatures:”記号,句点,*,*,*,*,。,。,。”, conjugationType:”*”, partOfSpeechLevel2:”句点”, conjugationForm:”*”, allFeaturesArray:{”記号”, “句点”, “*”, “*”, “*”, “*”, “。”, “。”, “。”}, partOfSpeechLevel4:”*”}}, endTime:”1502971949537″, |log|:”", processTime:”0″}

japaneseTokenize.frameworkをmacOS 10.10以降用にビルドしたバイナリを用意しましたので、自己責任で~/Library/Frameworksフォルダに入れて使ってみてください。

→ Download japaneseTokenize framework Binary

AppleScript名:japaneseTokenizeのじっけん
– Created 2017-08-17 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “japaneseTokenize”
–https://github.com/murakami/workbook/tree/master/mac/Ruby
–http://d.hatena.ne.jp/shu223/20130318/1363566717

–http://piyocast.com/as/archives/4778

set targString to “来週の水曜日に会議を予約。” –”Make a meeting on next Wednesday.” in Japanese

set aRes to current application’s jTokenize’s parseString:targString
–> {{token:”来週”, romaji:”raishuu”, hurigana:”らいしゅう”}, {token:”の”, romaji:”no”, hurigana:”の”}, {token:”水曜”, romaji:”suiyou”, hurigana:”すいよう”}, {token:”日”, romaji:”hi”, hurigana:”ひ”}, {token:”に”, romaji:”ni”, hurigana:”に”}, {token:”会議”, romaji:”kaigi”, hurigana:”かいぎ”}, {token:”を”, romaji:”wo”, hurigana:”を”}, {token:”予約”, romaji:”yoyaku”, hurigana:”よやく”}, {token:”。”, romaji:”。”, hurigana:”。”}}

set aList to (aRes’s valueForKeyPath:“hurigana”) as list –ふりがな
–>  {”らいしゅう”, “の”, “すいよう”, “ひ”, “に”, “かいぎ”, “を”, “よやく”, “。”}

set bList to (aRes’s valueForKeyPath:“token”) as list –元のテキスト
–>  {”来週”, “の”, “水曜”, “日”, “に”, “会議”, “を”, “予約”, “。”}

set cList to (aRes’s valueForKeyPath:“romaji”) as list –hurigana (romaji)
–>  {”raishuu”, “no”, “suiyou”, “hi”, “ni”, “kaigi”, “wo”, “yoyaku”, “。”}

set bRes to current application’s jTokenize’s parseStringWithLinguisticTag:targString
set dList to (bRes’s valueForKeyPath:“scheme”) as list
–>  {”Noun”, “Particle”, “Noun”, “Noun”, “Particle”, “Noun”, “Particle”, “Noun”, “SentenceTerminator”}

return bRes as list
–> {{token:”来週”, |scheme|:”Noun”}, {token:”の”, |scheme|:”Particle”}, {token:”水曜”, |scheme|:”Noun”}, {token:”日”, |scheme|:”Noun”}, {token:”に”, |scheme|:”Particle”}, {token:”会議”, |scheme|:”Noun”}, {token:”を”, |scheme|:”Particle”}, {token:”予約”, |scheme|:”Noun”}, {token:”。”, |scheme|:”SentenceTerminator”}}

★Click Here to Open This Script 

2017/08/10 テキストのキーワード検索(結果をNSRangeのarrayで返す)

指定のテキストをキーワードで検索し、結果をNSRangeのarray(list)で返すAppleScriptです。

結果をNSRangeのarray(list)で返す必要があったのは、NSTextView中のスタイル付きテキストを検索して該当箇所の色を変更するような処理のために作ったためです。

33Kバイトのテキストファイルのキーワード検索に0.002秒程度かかりました(MacBook Pro Retina 2012 Core i7 2.66GHz)。

AppleScript名:テキストのキーワード検索(結果をNSRangeのlistで返す)
– Created 2017-08-09 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4771

property NSString : a reference to current application’s NSString
property NSMutableArray : a reference to current application’s NSMutableArray
property NSLiteralSearch : a reference to current application’s NSLiteralSearch

set aStr to “ATGC ACGT ATGC AGTC
ATGC ACGT ATGC AGTC
ATGC ACGT ATGC AGTC
ATGC ACGT ATGC AGTC

set aRes to searchWordRanges(aStr, “ATGC”) of me as list
–>  {{location:0, length:4}, {location:10, length:4}, {location:20, length:4}, {location:30, length:4}, {location:40, length:4}, {location:50, length:4}, {location:60, length:4}, {location:70, length:4}}

on searchWordRanges(aTargText as string, aSearchStr as string)
  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
    
if (detectedRange’s location) is equal to (current application’s NSNotFound) then exit repeat
    
    
hitArray’s addObject:detectedRange
    
    
set aNum to (detectedRange’s location) 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
end searchWordRanges

★Click Here to Open This Script 

2017/06/17 PDFでテキスト検索してキーワードの存在ページをリストで返す

指定PDFで指定キーワードを検索して、キーワードが存在するページのノンブル(数値)のリストを返すAppleScriptです。

自分の書いた本のPDFファイル(483ページ)で検索を行なってみたところ、数秒程度はかかりました。

この手の処理では、同じScriptを実行しても2回目以降もとくにスピードアップしないので、ページごとに個別にテキスト抽出しておいて、配列に対してテキスト検索するほうが高速処理できると思われます。

配列変数上でページごとに分けておいたテキストに対して検索を行い、見つからなかった場合には仕方なく本ルーチンのような処理でPDFに対してテキスト検索を行うといったところでしょうか。

AppleScript名:PDFでテキスト検索してキーワードの存在ページをリストで返す
– Created 2016-01-05 10:17:51 by Shane Stanley
– Modified 2017-06-17 by Takaaki Naganoya
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “Quartz”
–http://piyocast.com/as/archives/4690

set aPath to POSIX path of (choose file of type {“com.adobe.pdf”})
set aSearchKeyword to “数値のインクリメント/デクリメント”
set guardPage to 15 –検索対象から外すページ(冒頭からこのページまでを除外)
set pRes to searchPDFforString(aPath, aString, guardPage) of me
–>  {67, 78}

–指定のPDFの指定のキーワードを検索してキーワードが存在するページのリストを返す
on searchPDFforString(posixPath, aSearchKeyword, guardPage)
  set theURL to current application’s |NSURL|’s fileURLWithPath:posixPath
  
set thePDF to current application’s PDFDocument’s alloc()’s initWithURL:theURL
  
  
set theSels to (thePDF’s findString:searchString withOptions:0)
  
set aList to {}
  
  
repeat with aSel in theSels
    set thePage to (aSel’s pages()’s objectAtIndex:0)’s label()
    
set curPage to (thePage as integer)
    
if curPage > guardPage then
      if curPage is not in aList then
        set the end of aList to curPage
      end if
    end if
  end repeat
  
  
return aList
end searchPDFforString

★Click Here to Open This Script 

2017/05/30 メールアドレスからMessage-IDらしきものを作成

与えられたメールアドレスから、メールヘッダに入るMessage-IDらしきものを生成するAppleScriptです。実行結果は、毎回変わります(そういう目的のために作ったので)。

高速メール送信が行えるWeb API「SendGrid」でメールの高速送信をAppleScriptから行ってみました。1通あたり0.2秒程度。さらに複数一括指定送信を行うと、AppleScriptからでも1通あたり0.007秒ぐらいで送信できたので(100通一括指定時)、たいへんに有用性が高いサービスだと感じられました。

そんなテストを繰り返す中、テスト送信先のメールアドレスがGmailのアドレスで、しかも1つのGMailアドレスに対して「+」でメールアドレス拡張を行ったときに、複数のメールが1つのメールとして見なされるという問題が起こりました。

その対処方法を探してみたところ、Twitter上で「Mail-HeaderにMessage-IDを入れるといいよ」と教えてもらいました。

Message-IDの指定について調べてみると、SendGrid呼び出し時にMessage-IDフィールドにメールアドレスをそのまま突っ込むといった乱暴なサンプルが見つかりました。とりあえず1回送るだけならこれでも問題がなさそうですが、ちゃんとユニークな値のMessage IDを入れないと、同じメールアドレスに複数回メールを送ったときに問題が発生する可能性がありそうでした。

厳密にRFC5322に準拠していなくても、それっぽくて一意に区別できれば問題ないものと考え、それっぽいものを作るAppleScriptを用意してみました。一応、MailCoreのプロジェクトの中にMessage-IDを作るメソッドがないかさがしてみたものの、単体で呼び出せるようなメソッドが見つからなかったので、まーこんなもんでしょう。

AppleScript名:メールアドレスからMessage-IDらしきものを作成
– Created 2017-05-30 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4672

set anAddress to “piyomarusoft+0001@gmail.com”
set anID to getMessageIDFromMailAddress(anAddress) of me
–>  ”<D6A128D3-2D53-4163-B2F7-53A25FB9608C@gmail.com>”

–とりあえずMessage IDらしきものを組み立ててみた
on getMessageIDFromMailAddress(anAddress as string)
  set anOffset to (offset of “@” in anAddress)
  
if anOffset = 0 then return false –Error
  
set aDomain to text anOffset thru -1 of anAddress
  
set aStr to (current application’s NSUUID’s UUID()’s UUIDString()) as string
  
return (“< " & aStr & aDomain & “>”)
end getMessageIDFromMailAddress

–offset命令の実行を横取りする
on offset of searchStr in str
  set aRes to getOffset(str, searchStr) of me
  
return aRes
end offset

on getOffset(str, searchStr)
  set d to divideBy(str, searchStr)
  
if (count d) is less than 2 then return 0
  
return (length of item 1 of d) + 1
end getOffset

on divideBy(str, separator)
  set delSave to AppleScript’s text item delimiters
  
set the AppleScript’s text item delimiters to separator
  
set strItems to every text item of str
  
set the AppleScript’s text item delimiters to delSave
  
return strItems
end divideBy

★Click Here to Open This Script 

2017/05/18 16進数文字列を10進数数値に変換するv2

16進数文字列をCocoaの機能を利用して10進数数値に変換するAppleScriptです。

以前、Pure AppleScriptで組んだものがありましたが、ためしにCocoaの機能を利用して実行速度を比較してみました。

 Pure AppleScript版:0.03秒ぐらい
 本AppleScript:0.02秒ぐらい

Cocoaの機能を呼び出すAppleScriptObjCのScriptは、Pure AppleScriptにくらべて1回目の実行がやや遅くなるものの、同じ処理を複数回呼び出すと2回目以降の速度が大幅に上がる傾向があります。繰り返し実行する場合にはAppleScriptObjC版のほうがおすすめです。

AppleScript名:16進数文字列を10進数数値に変換するv2
– Created 2017-05-18 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4645

set aHex to “0×9AC”
set hNum to retIntFromHexString(aHex) of me

on retIntFromHexString(aHex as string)
  set {aRes, hexRes} to (current application’s NSScanner’s scannerWithString:aHex)’s scanHexInt:(reference)
  
if aRes = false then
    return “” –エラーの場合
  else
    return hexRes
  end if
end retIntFromHexString

★Click Here to Open This Script 

2017/05/15 感情推定(極性判定) v2

apitoreのREST API「感情推定(極性判定) v2」を呼び出すAppleScriptです。

400文字以下のテキストを解析して、極性(感情)を判定します。ただし、文章が短すぎると判定しづらくなる傾向があるようです。これまでの感情推定APIではpositive/negativeとして判定する傾向があったため、このv2 APIではneutralとして判定する幅を広めにとってもらいました。

本Scriptをテストするためには、apitoreにサインアップしてAccess Tokenを取得し、Script末尾の伏字部分にコピー&ペーストしてください(掲載リストをそのまま実行してもエラーになります)。

AppleScript名:感情推定(極性判定) v2
– Created 2017-05-15 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4639

set aString to "このePubの考え方にそぐわない文章要素がいろいろある。代表的なものは「表」だ。データを掲載しておくのに、「表」は便利な存在だ。書く方からしても、いちいち長々とした文章で説明しなくていいし、読む方からしても整理された情報を読めるわけで、とくにお年寄りに好まれる。ところが、この「表」がePubの出版物にそぐわない。表の幅は変わってほしくないし、文字サイズが大きくなって数ページにわたって表示されるといったことは好ましくない。"
set a1Res to getSentimentFromString(aString) of me
–>  "positive"(なのか?)

on getSentimentFromString(aString)
  set reqURLStr to "https://api.apitore.com/api/39/sentiment-v2/predict"
  
set accessToken to retAccessToken() of me
  
set aRec to {access_token:accessToken, |text|:aString}
  
set aURL to retURLwithParams(reqURLStr, aRec) of me
  
  
set aRes to callRestGETAPIAndParseResults(aURL) of me
  
  
set aRESCode to (responseCode of aRes) as integer
  
if aRESCode is not equal to 200 then return false
  
  
set aRESTres to ((json of aRes)’s valueForKeyPath:"predict.sentiment") as string
  
return aRESTres
end getSentimentFromString

–GET methodのREST APIを呼ぶ
on callRestGETAPIAndParseResults(aURL)
  set aRequest to current application’s NSMutableURLRequest’s requestWithURL:(current application’s |NSURL|’s URLWithString:aURL)
  
aRequest’s setHTTPMethod:"GET"
  
aRequest’s setCachePolicy:(current application’s NSURLRequestReloadIgnoringLocalCacheData)
  
aRequest’s setHTTPShouldHandleCookies:false
  
aRequest’s setTimeoutInterval:60
  
aRequest’s setValue:"application/json" forHTTPHeaderField:"Accept"
  
  
set aRes to current application’s NSURLConnection’s sendSynchronousRequest:aRequest returningResponse:(reference) |error|:(missing value)
  
set resList to aRes as list
  
  
set bRes to contents of (first item of resList)
  
set resStr to current application’s NSString’s alloc()’s initWithData:bRes encoding:(current application’s NSUTF8StringEncoding)
  
  
set jsonString to current application’s NSString’s stringWithString:resStr
  
set jsonData to jsonString’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aJsonDict to current application’s NSJSONSerialization’s JSONObjectWithData:jsonData options:0 |error|:(missing value)
  
  
–Get Response Code & Header
  
set dRes to contents of second item of resList
  
if dRes is not equal to missing value then
    set resCode to (dRes’s statusCode()) as number
    
set resHeaders to (dRes’s allHeaderFields()) as record
  else
    set resCode to 0
    
set resHeaders to {}
  end if
  
  
return {json:aJsonDict, responseCode:resCode, responseHeader:resHeaders}
end callRestGETAPIAndParseResults

on retURLwithParams(aBaseURL, aRec)
  set aDic to current application’s NSMutableDictionary’s dictionaryWithDictionary:aRec
  
set aKeyList to (aDic’s allKeys()) as list
  
set aValList to (aDic’s allValues()) as list
  
set aLen to length of aKeyList
  
  
set qList to {}
  
repeat with i from 1 to aLen
    set aName to contents of item i of aKeyList
    
set aVal to contents of item i of aValList
    
set the end of qList to (current application’s NSURLQueryItem’s queryItemWithName:aName value:aVal)
  end repeat
  
  
set aComp to current application’s NSURLComponents’s alloc()’s initWithString:aBaseURL
  
aComp’s setQueryItems:qList
  
set aURL to (aComp’s |URL|()’s absoluteString()) as text
  
  
return aURL
end retURLwithParams

on retAccessToken()
  return "xxXXXxxX-XxXx-XXXX-xXXX-XXxXXxxXxxXx" –API Tore Access Token
end retAccessToken

★Click Here to Open This Script 

2017/04/11 指定のテキストから言語コードや言語名を取得する

Shane Stanleyによる、指定テキストを識別して言語コードや言語名を取得するAppleScriptです。

割とアラビア語のテキストを判定するのに文字数が必要だったのが意外です。

以前に掲載した「2015/09/10 テキストの言語を推測する」よりも若干(1行ぐらい)短くなっています。

AppleScript名:指定のテキストから言語コードや言語名を取得する
– Created 2017-04-10 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”

set str1 to “Ilmatyynyalukseni on täynnä ankeriaita.”
set str2 to “Luftputebåten min er full av ål”
set str3 to “私の名前は長野谷です。”
set str4 to أنشأ فوكوزاوا يوكيتشي (١٨٣٥-١٩٠١) في اكتوبر عام ١٨٥٨ مدرسة للدراسات الهولندية (تحولت بعد ذلك لمدرسة للغة الانكليزية) في ايدو (طوكيو حاليا). يعد فوكوزاوا يوكيتشي من أحد مؤسسي نهضة اليابان الحديثة، فونهتم بمدرستنا بنوع التعليم الذي ينمي القدرات الإبداعية والفنية التي يتطلب توافرها في طلاب الجامعة بحيث لا ينشغل الطلاب باختبار قبول الجامعات ونحترم استقلالية وتفرد كل طالب وذلك في جو دافئ في بيئة طبيعية مليئة بأشجار
set str5 to 게이오 기주쿠는 어디에나 있는 학교의 하나로 만족하지 않습니다. 게이오 기주쿠는 기주쿠(義塾, 의숙)에서 배우는 학생과 교원이 일본의기품의 원천지덕의 모범 되는 것을 목표로 하는 학숙(學塾)입니다. “
set str6 to 庆应义塾不是仅仅满足于成常常到的一般性学校。”

set a1Res to my guessLanguageCodeOf:str1 –>  ”fi”
set a2Res to my guessLanguageCodeOf:str2 –>  ”sv”
set a3Res to my guessLanguageCodeOf:str3 –>  ”ja”
set a4Res to my guessLanguageCodeOf:str4 –>  ”ar”
set a5Res to my guessLanguageCodeOf:str5 –>  ”ko”
set a6Res to my guessLanguageCodeOf:str6 –>  ”zh-Hans”

set b1Res to my guessLanguageOf:str1 –>  ”Finnish”
set b2Res to my guessLanguageOf:str2 –>  ”Swedish”
set b3Res to my guessLanguageOf:str3 –>  ”Japanese”
set b4Res to my guessLanguageOf:str4 –>  ”Arabic”
set b5Res to my guessLanguageOf:str5 –>  ”Korean”
set b6Res to my guessLanguageOf:str6 –>  ”Chinese”

on guessLanguageOf:theString
  set theTagger to current application’s NSLinguisticTagger’s alloc()’s initWithTagSchemes:{current application’s NSLinguisticTagSchemeLanguage} options:0
  
theTagger’s setString:theString
  
set languageID to theTagger’s tagAtIndex:0 |scheme|:(current application’s NSLinguisticTagSchemeLanguage) tokenRange:(missing value) sentenceRange:(missing value)
  
return ((current application’s NSLocale’s localeWithLocaleIdentifier:“en”)’s localizedStringForLanguageCode:languageID) as text
end guessLanguageOf:

on guessLanguageCodeOf:theString
  set theTagger to current application’s NSLinguisticTagger’s alloc()’s initWithTagSchemes:{current application’s NSLinguisticTagSchemeLanguage} options:0
  
theTagger’s setString:theString
  
set languageID to theTagger’s tagAtIndex:0 |scheme|:(current application’s NSLinguisticTagSchemeLanguage) tokenRange:(missing value) sentenceRange:(missing value)
  
return languageID as text
end guessLanguageCodeOf:

★Click Here to Open This Script 

2017/03/29 URLの文字列から(主に後ろに)スペースでつながった不要部分が存在する場合には除去

URL文字列の後ろから、スペース文字(%20)でつながった不要部分が存在する場合には除去するAppleScriptです。

Wikipedia上で自分が書いた記事内のリンク先URLを自動でチェックしたくて、REST API経由でWikipediaの記事のソース文字列を取得し、NSDataDetectorを呼び出してURLのみを抽出。抽出したURLが実際に存在するか、存在する場合でもForwardされていないかどうかといった情報をチェックするAppleScriptを書いてみました(このぐらいは余裕)。

ところが、書いたAppleScriptを実行すると大量にエラーのURLが見つかりました。エラーになったURLを実際に確認してみても、ページは存在するし削除されたわけでもなさそうでした。

さらに調査してみると、NSDataDetectorがWikipediaのソース文字列からURLを抽出するときに、URLのあとに半角スペースに続いて英数字が存在すると「URLの一部」としてURLエンコーディングしてURLの一部として取り込んでしまっているようでした。

そこで、いったん存在確認でNGが出たURLについては、本ルーチンでクリーニングを行って再度存在確認を実施するようにしてみました。このように対策してみると、きちんとURLの存在を確認できました。

AppleScript名:URLの文字列から(主に後ろに)スペースでつながった不要部分が存在する場合には除去
– Created 2017-03-29 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4571

set aStr to “https://developer.apple.com/library/mac/documentation/” & “AppleScript/Conceptual/AppleScriptX/AppleScriptX.html%20AppleScript%20Overview”
set aRes to cleaningURLStr(aStr) of me
–>
(*
“https://developer.apple.com/library/mac/documentation/
AppleScript/Conceptual/AppleScriptX/AppleScriptX.html”
*)

on cleaningURLStr(aStr)
  set anOffset to offset of “%20″ in aStr
  
if anOffset = 0 then return false
  
set bStr to text 1 thru (anOffset - 1) of aStr
  
return bStr
end cleaningURLStr

★Click Here to Open This Script 

2017/03/27 実体参照している文字列をデコードする(ASOC)

HTMLの中などで実体参照(Character reference)しているエンコードされた文字列をデコードするAppleScriptのCocoa呼び出し版です(Pure AppleScript版はこちら)。

AppleScript名:実体参照している文字列をデコードする(ASOC)
– Created 2017-01-18 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4568

set aStr to "&#40845;&#39340;&#20253;"
set aRes to decodeCharacterReference(aStr) of me
–>  "龍馬伝"

set aStr to "\"&#31532;2&#12456;&#12450;\""
set aRes to decodeCharacterReference(aStr) of me
–>  "\"第2エア\""

on decodeCharacterReference(aStr)
  set anNSString to current application’s NSString’s stringWithString:aStr
  
set theData to anNSString’s dataUsingEncoding:(current application’s NSUTF16StringEncoding)
  
set styledString to current application’s NSAttributedString’s alloc()’s initWithHTML:theData documentAttributes:(missing value)
  
set plainText to (styledString’s |string|()) as string
  
return plainText
end decodeCharacterReference

★Click Here to Open This Script 

2017/03/18 文字を集合(CountedSet)に変換して文字列同士の近似度を計算する v2

複数の文字列同士の近似度を擬似的に計算できないかと考えて、文字列をCountedSetに変換して、CountedSet同士でand演算(intersectSet)を実行。その計算結果が大きいほど「文字列中に含まれている文字列の傾向が似ている」と判断するテストのAppleScriptです。

countedset_intersect_resized.png

とりあえずはintersectSetで積集合を計算しています。重複している部分を求めているわけです。

 「This is an apple.」と「This is a pinapple.」–> 11
 「This is a pinapple.」と「Piyomaru San Dayo.」–> 5
 「Piyomaru San Dayo.」と「This is an apple.」–> 5

v1とv2の計算結果を合わせて、両方の傾向を反映させるようにするとよいのかもしれません。

AppleScript名:文字を集合(CountedSet)に変換して文字列同士の近似度を計算する v2
– Created 2017-03-18 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4532

set aStr to "This is a pinnapple."
set bStr to "This is an apple."
set cStr to "Piyomaru San Dayo."

set a1Res to getApproximationBetweenStringsIntersect(aStr, bStr) of me
–>  11

set bRes to getApproximationBetweenStringsIntersect(bStr, cStr) of me
–>  5

set cRes to getApproximationBetweenStringsIntersect(cStr, aStr) of me
–>  5

on getApproximationBetweenStringsIntersect(aStr, bStr)
  set aList to current application’s NSMutableArray’s arrayWithArray:(characters of aStr)
  
set bList to current application’s NSMutableArray’s arrayWithArray:(characters of bStr)
  
  
set aIndex to current application’s NSCountedSet’s alloc()’s initWithArray:aList
  
set bIndex to current application’s NSCountedSet’s alloc()’s initWithArray:bList
  
  
aIndex’s intersectSet:bIndex
  
set aRes to aIndex’s allObjects()’s |count|()
  
return aRes
end getApproximationBetweenStringsIntersect

★Click Here to Open This Script 

2017/03/18 文字を集合(CountedSet)に変換して文字列同士の近似度を計算する v1

複数の文字列同士の近似度を擬似的に計算できないかと考えて、文字列をCountedSetに変換して、CountedSet同士で減算を実行。その計算結果が少ないほど「文字列中に含まれている文字列の傾向が似ている」と判断するテストのAppleScriptです。

countedset.png

とりあえずはminusSetで減算を行なっていますが、ほかの方法も試してみたいところです。

本Scriptでは、得られた結果の数値が小さければ重複している文字が多いということで、計算結果そのものにはあまり意味はありませんが、複数の結果を大小比較して、数値が小さいもののペアが「似たような傾向を持つもの」として期待できます。

 「This is an apple.」と「This is a pinapple.」–> 3
 「This is a pinapple.」と「Piyomaru San Dayo.」–> 8
 「Piyomaru San Dayo.」と「This is an apple.」–> 9

ということで、これらの間では「This is an apple.」と「This is a pinapple.」の近似度が一番高いといえることになります。

AppleScript名:文字を集合(CountedSet)に変換して文字列同士の近似度を計算する v1
– Created 2017-03-18 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4530

set aStr to “This is a pinnapple.”
set bStr to “This is an apple.”
set cStr to “Piyomaru San Dayo.”

set a1Res to getApproximationBetweenStrings(aStr, bStr) of me
–>  3

set bRes to getApproximationBetweenStrings(bStr, cStr) of me
–>  8

set cRes to getApproximationBetweenStrings(cStr, aStr) of me
–>  9

on getApproximationBetweenStrings(aStr, bStr)
  set aList to current application’s NSMutableArray’s arrayWithArray:(characters of aStr)
  
set bList to current application’s NSMutableArray’s arrayWithArray:(characters of bStr)
  
  
set aIndex to current application’s NSCountedSet’s alloc()’s initWithArray:aList
  
set bIndex to current application’s NSCountedSet’s alloc()’s initWithArray:bList
  
  
aIndex’s minusSet:bIndex
  
set aRes to aIndex’s allObjects()’s |count|()
  
  
bIndex’s minusSet:aIndex
  
set bRes to bIndex’s allObjects()’s |count|()
  
  
if aRes bRes then
    return bRes
  else
    return aRes
  end if
end getApproximationBetweenStrings

★Click Here to Open This Script 

2017/02/16 YAMLのじっけん

オープンソースのフレームワーク「YAML.framework」(Mirek Rusin)を用いて、構造化データ記述言語であるYAMLとオブジェクト(listやrecord)との間の相互変換を行うAppleScriptです。

これまでYAMLとは縁のない生活を送ってきましたが、とくに問題はありませんでした。構造を持つデータの分量が増えた場合には、プログラム中に直接記述せず、Excelの表からデータを読み取って処理したり、データベースなど他のデータソースからデータを取得していました。

ただ、listとかrecordの内容が込み入ってくる(フィールド数が多いとか、入れ子構造の段数が深いとか)と、こういう仕組みがあったほうが便利なんだろうな、ということは理解できます。

YAML.frameworkのプロジェクトをXcodeでビルドし、出来上がったフレームワークを~/Library/Frameworksに入れてテストしてみてください。

途中、Githubに掲載されているObjective-CのサンプルプログラムがNSInputStreamを使っており、これをAppleScriptに書き換えて呼び出すとAppleScriptの処理系が100%クラッシュ。NSDataを経由して変換する方法なども試してみましたがNSInputStreamを作りにいくとクラッシュ。

結局、Stringから直接変換するメソッドがあったため、これを使用することで安定して処理できるようになりました。

当初は機械学習フレームワークの「YCML」をいじくっていたのですが、その過程で本フレームワークを発見。なかなか有用性が高そうなので試してみた次第です。

AppleScript名:YAMLのじっけん
– Created 2017-02-16 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “YAML” –https://github.com/mirek/YAML.framework
–http://piyocast.com/as/archives/4464

–YAMLの文字列からオブジェクトを生成する
set aYAMLstr to
items:
- name: Foo
- name: Bar

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

set aData to current application’s YAMLSerialization’s objectsWithYAMLString:aStr options:(4096) |error|:(missing value)
–>  (NSArray) {{items:{{name:”Foo”}, {name:”Bar”}}}}

–オブジェクトからYAMLの文字列を取得する
set aString to (current application’s YAMLSerialization’s createYAMLStringWithObject:aData options:(1) |error|:(missing value)) as string
(* –>
“—
- items:
- name: Foo
- name: Bar
…”
*)

★Click Here to Open This Script 

AppleScript名:YAMLのじっけん4
– Created 2017-02-16 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “YAML” –https://github.com/mirek/YAML.framework
–http://piyocast.com/as/archives/4464

–YAMLの文字列からオブジェクトを生成する
set aYAMLstr to
- name: Smith
email: smith@mail.com
- name: Shelton
email: shelton@mail.com
- name: Kelly
email: kelly@mail.com

set aRes to retObjectFromYAMLString(aYAMLstr) of me
–>  {{{name:”Smith”, email:”smith@mail.com”}, {name:”Shelton”, email:”shelton@mail.com”}, {name:”Kelly”, email:”kelly@mail.com”}}}

set bYAMLstr to
names: [Smith, Shelton, Kelly]
emails: [smith@mail.com, shelton@mail.com, kelly@mail.com]

set bRes to retObjectFromYAMLString(bYAMLstr) of me
–>  {{names:{”Smith”, “Shelton”, “Kelly”}, emails:{”smith@mail.com”, “shelton@mail.com”, “kelly@mail.com”}}}

–オブジェクト(list)からYAMLの文字列を生成する
set bStr to retYAMLStringFromObject(bRes)
(*  
“—
- names:
- Smith
- Shelton
- Kelly
emails:
- smith@mail.com
- shelton@mail.com
- kelly@mail.com


*)

on retObjectFromYAMLString(aYAMLstr as string)
  set aStr to current application’s NSString’s stringWithString:aYAMLstr
  
set aData to aStr’s dataUsingEncoding:(current application’s NSUTF8StringEncoding)
  
set aData to current application’s YAMLSerialization’s objectsWithYAMLString:aStr options:(4096) |error|:(missing value)
  
return aData as list
end retObjectFromYAMLString

on retYAMLStringFromObject(anObject)
  set aString to (current application’s YAMLSerialization’s createYAMLStringWithObject:anObject options:(1) |error|:(missing value)) as string
  
return aString
end retYAMLStringFromObject

★Click Here to Open This Script 

2017/02/08 指定文字列一括消去

複数の文字列を一括で消去するAppleScriptです。

unicodechecker_65532.png

ASOCでどうしても置換できないゴミ文字列(string id 65532)に遭遇し、Cocoaベースの置換ルーチンからPure AppleScriptの置換ルーチンに置き換えることになりました。

その折、複数回の置換処理を行なっている部分が気になったので、複数の文字を一括で置換するようにしてみたのが本ルーチンです。消去対象の文字列同士のコンフリクトなどは一切チェックしていません。

当初、一括置換ルーチンも掲載していたのですが、動作内容に納得がいかなかったので削除しました。

AppleScript名:指定文字列一括消去
– Created 2017-02-08 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4445

set aText to “Gundam, Guncannon, Guntank, Zak, Gufu, Gogg, Gergog, Zeong, Neo-Zeong”
set deleteTargList to {“Gun”, “Zeon”}

set aRes to removeChars(aText, deleteTargList) of me
–> “dam, cannon, tank, Zak, Gufu, Gogg, Gergog, g, Neo-g”

–指定文字列一括消去
on removeChars(origText as string, targStr as list)
  set aLen to length of targStr
  
set repStr to {}
  
repeat with i from 1 to aLen
    set the end of repStr to “”
  end repeat
  
  
set {txdl, AppleScript’s text item delimiters} to {AppleScript’s text item delimiters, targStr}
  
set temp to text items of origText
  
set AppleScript’s text item delimiters to repStr
  
set res to temp as text
  
set AppleScript’s text item delimiters to txdl
  
return res
end removeChars

★Click Here to Open This Script 

2017/01/05 XPathQuery4ObjCのじっけん

オープンソースのXML/RSSのパーサー「XPathQuery for Objective-C」(By Satoshi Konno)をCocoa FrameworkにビルドしてAppleScriptから呼び出してみました。

いくつも試してはみたものの、なかなかしっくりくるRSSのパーサーがなかったのですが、これまで試した中では一番いい感じです(当社比)。

とりあえず、OS X 10.10以降で動作するようにXPathQuery4ObjCをFramework化してビルドしたバイナリを用意しておきました。自己責任でframeworkを~/Library/Frameworksフォルダ以下にアーカイブを展開してコピーしたうえで本Scriptをおためしください。

–> Download Framework Binary

AppleScript名:XPathQuery4ObjCのじっけん
– Created 2017-01-05 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “XPathQueryKit”
–https://github.com/cybergarage/XPathQuery4ObjC
–http://piyocast.com/as/archives/4376

set rssURL to current application’s |NSURL|’s URLWithString:“http://rss.news.yahoo.com/rss/topstories”
set xpathQuery to current application’s CGXPathQuery’s alloc()’s initWithContentsOfURL:rssURL

if ((xpathQuery’s parse()) as boolean) = true then
  set aTitle to xpathQuery’s valueForXPath:“/rss/channel/title”
  
set entriesList to xpathQuery’s objectsForXPath:“/rss/channel/item”
  
  
repeat with xpathObject in entriesList
    
    
set entryTitle to (xpathObject’s valueForXPath:“title”)
    
log {entryTitle as string}
    
    
set linkURL to (current application’s |NSURL|’s URLWithString:(xpathObject’s valueForXPath:“link”))
    
log {linkURL’s absoluteString() as string}
    
    
set mediaContent to (xpathObject’s objectForXPath:“media:content”)
    
    
if mediaContent is not equal to missing value then
      set anAttr to mediaContent’s attributes()
      
set anImageURL to (current application’s |NSURL|’s URLWithString:((mediaContent’s attributes())’s valueForKey:“url”))
      
log {anImageURL’s absoluteString() as string}
    end if
    
  end repeat
  
end if

★Click Here to Open This Script 

AppleScript名:XPathQuery4ObjCのじっけん2
– Created 2017-01-05 by Takaaki Naganoya
– 2017 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “XPathQueryKit”
–https://github.com/cybergarage/XPathQuery4ObjC
–http://piyocast.com/as/archives/4376

set rssURL to current application’s |NSURL|’s URLWithString:“http://piyocast.com/as/feed”
set xpathQuery to current application’s CGXPathQuery’s alloc()’s initWithContentsOfURL:rssURL

if ((xpathQuery’s parse()) as boolean) = true then
  set entriesList to xpathQuery’s objectsForXPath:“/rss/channel/item”
  
  
repeat with itemObject in entriesList
    set aChild to itemObject’s children()
    
log aChild
    
    
set aTitle to (itemObject’s valueForXPath:“title”)
    
log aTitle as string
    
    
set aLink to (itemObject’s valueForXPath:“link”)
    
log aLink as string
    
    
set aComments to (itemObject’s valueForXPath:“comments”)
    
log aComments as string
    
    
set aPubdate to (itemObject’s valueForXPath:“pubDate”)
    
log aPubdate as string
    
    
set aCreator to (itemObject’s valueForXPath:“dc:creator”)
    
log aCreator as string
    
    
set aCategory to (itemObject’s valuesForXPath:“category”)
    
log aCategory as list
    
    
set aGUID to (itemObject’s valuesForXPath:“guid”)
    
log aGUID as string
    
    
set aDesc to (itemObject’s valueForXPath:“description”)
    
log aDesc as string
    
    
set aCommentURL to (itemObject’s valueForXPath:“wfw:commentRss”)
    
log aCommentURL as string
  end repeat
  
end if

★Click Here to Open This Script 

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

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

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

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

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

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

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

★Click Here to Open This Script 

2016/12/14 &と=で区切られたテキストをrecordに 改

「aParam=1234&bParam=2345」のように、URLのパラメータ部のような文字列を「&」と「=」で区切ってrecordとして出力するAppleScriptです。

「&と=で区切られたテキストをrecordに(NSScanner版)」をedama2さんがさらに改良したものです。

「先日掲載されたNSScannerのスクリプト、自分でも試してみてとても便利だったんですが、値なしの時にエラーになるので修正してみました。

あとリファレンス本に「repeat until」はあんまり使わないと書いてあったので使ってみました。(edama2さん)」

なるほど。たしかにラベルだけで値が存在しないケースに対応できるとよさそうです。

AppleScript名:&と=で区切られたテキストをrecordに 改
– Created 2016-12-12 by Shane Stanley
– Modified 2016-12-14 by edama2
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4358

set aParamStr to "access_token=XXxxx(XXxXXXXXxxXxxXXx))&expires=86399&name="
set aDict to (parseStrByAmpAndEqual(aParamStr) of me)
–>  {expires:"86399", |name|:"", access_token:"XXxxx(XXxXXXXXxxXxxXXx))"}

on parseStrByAmpAndEqual(aParamStr)
  set theScanner to current application’s NSScanner’s scannerWithString:aParamStr
  
set aDict to current application’s NSMutableDictionary’s |dictionary|()
  
  
repeat until (theScanner’s isAtEnd as boolean)
    – terminate check, return the result (aDict) to caller
    
set {theResult, theKey} to theScanner’s scanUpToString:"=" intoString:(reference)
    
    
– skip over separator
    
theScanner’s scanString:"=" intoString:(missing value)
    
set {theResult, theValue} to theScanner’s scanUpToString:"&" intoString:(reference)
    
if theValue is missing value then set theValue to "" –>追加
    
    
– skip over separator
    
theScanner’s scanString:"&" intoString:(missing value)
    
    
aDict’s setObject:theValue forKey:theKey
  end repeat
  
  
return aDict as record
end parseStrByAmpAndEqual

★Click Here to Open This Script 

2016/12/13 顔認識結果のデータを加工

Microsoftの顔認識APIを呼び出して、その結果を加工して表情認識APIを呼び出すために、顔認識APIの実行結果を加工するAppleScriptです。

まだ表情認識APIは呼び出せていませんが、その途中で作ったものです。

典型的なデータ加工の処理なので、ほかにも(もう少しうまい)やり方がありそうです。

AppleScript名:顔認識結果のデータを加工
– Created 2016-12-13 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
–http://piyocast.com/as/archives/4357

set aList to current application’s NSArray’s arrayWithArray:{{faceId:"c49883a0-c4ee-4671-ad06-6f7f8370ab20", faceRectangle:{top:68, width:40, |left|:336, height:40}}, {faceId:"93459b48-4c19-4cc7-85e6-b38b8d0fc73b", faceRectangle:{top:81, width:38, |left|:92, height:38}}, {faceId:"9c796619-c654-47dd-b7b0-c382dc6bda84", faceRectangle:{top:63, width:38, |left|:214, height:38}}, {faceId:"94a2ceec-c089-494c-982e-a213579342b6", faceRectangle:{top:92, width:38, |left|:462, height:38}}, {faceId:"18e8dad0-fe46-4c5c-81c3-3ba483129512", faceRectangle:{top:89, width:37, |left|:700, height:37}}, {faceId:"4031f4bd-4e47-4050-89a0-f66e2e4e9142", faceRectangle:{top:45, width:37, |left|:570, height:37}}}

set aRes to retFaceArray(aList) of me
–>  "336,68,40,40;92,81,38,38;214,63,38,38;462,92,38,38;700,89,37,37;570,45,37,37"

on retFaceArray(aList)
  set a1Res to (aList’s valueForKeyPath:"faceRectangle.top") as list
  
set a2Res to (aList’s valueForKeyPath:"faceRectangle.width") as list
  
set a3Res to (aList’s valueForKeyPath:"faceRectangle.left") as list
  
set a4Res to (aList’s valueForKeyPath:"faceRectangle.height") as list
  
  
set aLen to length of a1Res
  
set aRes to {}
  
  
repeat with i from 1 to aLen
    set aLeft to (contents of item i of a3Res) as string
    
set aTop to (contents of item i of a1Res) as string
    
set aWidth to (contents of item i of a2Res) as string
    
set aHeight to (contents of item i of a4Res) as string
    
set tmpRes to aLeft & "," & aTop & "," & aWidth & "," & aHeight
    
set the end of aRes to tmpRes
  end repeat
  
  
set allRes to retStrFromArrayWithDelimiter(aRes, ";") of me
  
  
return allRes
end retFaceArray

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

★Click Here to Open This Script 

2016/12/12 &と=で区切られたテキストをrecordに(NSScanner版)

「aParam=1234&bParam=2345」のように、URLのパラメータ部のような文字列を「&」と「=」で区切ってrecordとして出力するAppleScriptです。

Shane Stanleyから「NSScannerを使った実装例」を教えてもらいました。「高速ではないけど、NSSCannerは便利なクラスなので慣れておいたほうがいいよ」という実例です。

Cocoaには、AppleScriptの処理系にはまったく存在していない強力な機能があり、使うととても便利で、プログラムを短く書くことができ、圧倒的に高速処理が可能です。

 リスト項目のユニーク化:NSSet
 リスト項目のユニーク化と出現回数カウント:NSCountedSet
 リスト項目のユニーク化とソート:NSMutableIndexSet
 リスト項目のしぼりこみ:NSPredicate

など、もはやプログラムを書く上で「ないと困る!」というレベルの道具になっています。ファイルパスの加工が強力なNSStringもないと困るレベルのものです。

NSScannerについて、Shaneのプログラムをそのまま掲載するとこんな感じですが、これは「巨大なプログラムを書き慣れた人が書く」タイプのプログラムであり、個別に理解を深めようとすると・・・つまり、「教材」として提示する際には若干の清書が必要になります。

AppleScript名:&と=で区切られたテキストをrecordに(NSScanner Orig)
– Created 2016-12-12 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4356

set aParamStr to “access_token=XXxxx(XXxXXXXXxxXxxXXx))&expires=86399″
set aDict to (parseStrByAmpAndEqual(aParamStr) of me) as record

on parseStrByAmpAndEqual(aParamStr)
  set theScanner to current application’s NSScanner’s scannerWithString:aParamStr
  
set aDict to current application’s NSMutableDictionary’s |dictionary|()
  
repeat
    set {theResult, theKey} to theScanner’s scanUpToString:“=” intoString:(reference)
    
if theResult as boolean is false then return aDict
    
– skip over separator
    
theScanner’s scanString:“=” intoString:(missing value)
    
set {theResult, theValue} to theScanner’s scanUpToString:“&” intoString:(reference)
    
– skip over separator
    
theScanner’s scanString:“&” intoString:(missing value)
    
aDict’s setObject:theValue forKey:theKey
  end repeat
end parseStrByAmpAndEqual

★Click Here to Open This Script 

自分が動作内容を確認するためには、こんな感じに

(1)意味を取りやすいように空行を追加
(2)プログラムの動きを理解しやすいように、さまざまな変数のログ表示を行う
(3)プログラムのInputとOutputがわかりやすいように、サンプルデータの処理結果を掲載

のように書き換えます。

AppleScript名:&と=で区切られたテキストをrecordに(NSScanner 2)
– Created 2016-12-12 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4356

set aParamStr to “access_token=XXxxx(XXxXXXXXxxXxxXXx))&expires=86399″
set aDict to (parseStrByAmpAndEqual(aParamStr) of me) as record
–>  {expires:”86399″, access_token:”XXxxx(XXxXXXXXxxXxxXXx))”}

on parseStrByAmpAndEqual(aParamStr)
  set theScanner to current application’s NSScanner’s scannerWithString:aParamStr
  
log (theScanner’s scanLocation())
  
  
set aDict to current application’s NSMutableDictionary’s |dictionary|()
  
repeat
    – terminate check, return the result (aDict) to caller
    
set {theResult, theKey} to theScanner’s scanUpToString:“=” intoString:(reference)
    
if theResult as boolean is false then return aDict
    
log (theScanner’s scanLocation())
    
    
– skip over separator
    
theScanner’s scanString:“=” intoString:(missing value)
    
set {theResult, theValue} to theScanner’s scanUpToString:“&” intoString:(reference)
    
log (theScanner’s scanLocation())
    
    
– skip over separator
    
theScanner’s scanString:“&” intoString:(missing value)
    
    
aDict’s setObject:theValue forKey:theKey
    
log (theScanner’s scanLocation())
  end repeat
end parseStrByAmpAndEqual

★Click Here to Open This Script 

そして、最終的に確認用のコードを削除して(クリーニングして)、こういうリストを掲載しています。

AppleScript名:&と=で区切られたテキストをrecordに(NSScanner 1)
– Created 2016-12-12 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4356

set aParamStr to “access_token=XXxxx(XXxXXXXXxxXxxXXx))&expires=86399″
set aDict to (parseStrByAmpAndEqual(aParamStr) of me) as record
–>  {expires:”86399″, access_token:”XXxxx(XXxXXXXXxxXxxXXx))”}

on parseStrByAmpAndEqual(aParamStr)
  set theScanner to current application’s NSScanner’s scannerWithString:aParamStr
  
set aDict to current application’s NSMutableDictionary’s |dictionary|()
  
  
repeat
    – terminate check, return the result (aDict) to caller
    
set {theResult, theKey} to theScanner’s scanUpToString:“=” intoString:(reference)
    
if theResult as boolean is false then return aDict
    
    
– skip over separator
    
theScanner’s scanString:“=” intoString:(missing value)
    
set {theResult, theValue} to theScanner’s scanUpToString:“&” intoString:(reference)
    
    
– skip over separator
    
theScanner’s scanString:“&” intoString:(missing value)
    
    
aDict’s setObject:theValue forKey:theKey
  end repeat
end parseStrByAmpAndEqual

★Click Here to Open This Script 

2016/12/09 &と=で区切られたテキストをrecordに

「aParam=1234&bParam=2345」のように、URLのパラメータ部のような文字列を「&」と「=」で区切ってrecordとして出力するAppleScriptです。

とあるWeb APIでOAuth認証を呼び出してアクセストークンを取得したら、こんな形式の文字列で返してきた偏屈なもの(Stack ExchangeのAPI)があったので、対処のために作成したものです。

よく作る(作り捨てする)タイプの処理なので、たぶん何度も書いていると思います、、、、

AppleScript名:&と=で区切られたテキストをrecordに
– Created 2016-12-09 by Takaaki Naganoya
– 2016 Piyomaru Software
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
–http://piyocast.com/as/archives/4355

set aParamStr to “access_token=XXxxx(XXxXXXXXxxXxxXXx))&expires=86399″
set aDict to (parseStrByAmpAndEqual(aParamStr) of me) as record
–> {expires:”86399″, access_token:”XXxxx(XXxXXXXXxxXxxXXxx))”}

on parseStrByAmpAndEqual(aParamStr)
  set aStr to current application’s NSString’s stringWithString:aParamStr
  
set aList to (aStr’s componentsSeparatedByString:“&”) as list
  
set aDict to current application’s NSMutableDictionary’s alloc()’s init()
  
  
repeat with i in aList
    set j to (current application’s NSString’s stringWithString:(contents of i))
    
set {aLabel, aValue} to (j’s componentsSeparatedByString:“=”)
    (
aDict’s setValue:aValue forKey:aLabel)
  end repeat
  
  
return aDict
end parseStrByAmpAndEqual

★Click Here to Open This Script