本BlogのようなWordPressで運用されており、AppleScriptのURLリンクを記事に埋め込んでいるWordPressに対して、XML-RPC経由で指定IDの記事本文を取得し、埋め込まれているURLリンクからAppleScriptのソースコードを取得して、メモリー上でコンパイルして書式つきテキストに変換し、AppleScript構文書式をもとにpropertyラベルを抽出、そのうちCocoa Classのみをリストで取得するAppleScriptです。
本Blogに投稿した記事から宣言しているCocoa Classを抽出し、自動でタグ付けするために準備したものです。1記事に対して複数のAppleScriptが掲載されている場合にも対応しています。
HTMLReader.frameworkを用いてBlog本文からのリンク抽出、リンクURL抽出を行っています。
–> HTMLReader.framework(To ~/Library/Frameworks/)
本Sample Scriptで指定したIDの記事のproperty部分はこのようになっており、
本Scriptの実行によって抽出されたCocoa Class(単なる変数的なproperty項目や、Enumは対象外のため排除)は、
–> {“NSString”, “NSArray”, “OSAScript”, “NSPredicate”, “OSALanguage”, “NSDictionary”, “OSALanguageInstance”, “NSBundle”, “NSUnarchiver”}
のようになります。自分の環境でMacBook Proを有線ネットワーク接続した状態で、3.3〜3.4秒程度かかっています。大量の記事を処理する場合には本AppleScriptの並列処理を行うと処理時間の大幅な短縮が期待できます(MacのCPUがサーマルスロットリングで速度低下しなければ)。
また、負荷が集中している特定コアの動作周波数を上げ、他のコアの動作周波数を落とすTurbo Boostが有効な状態で並列処理を実行すると、並列処理を行う意義そのものが低下してしまうため、Turbo-Boost-Switcherのようなツールの併用が必要と思われます。
AppleScript名:WordPressの指定IDの記事にリンクされているapplescriptからCocoa Classのproperty宣言を抽出 v3 |
— Created 2018-07-30 by Takaaki Naganoya — Modified 2018-08-05 by Takaaki Naganoya — 2018 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" use framework "AppKit" use framework "OSAKit" use framework "HTMLReader" –https://github.com/nolanw/HTMLReader property |NSURL| : a reference to current application’s |NSURL| property NSArray : a reference to current application’s NSArray property NSString : a reference to current application’s NSString property NSBundle : a reference to current application’s NSBundle property NSThread : a reference to current application’s NSThread property OSAScript : a reference to current application’s OSAScript property NSPredicate : a reference to current application’s NSPredicate property NSTextView : a reference to current application’s NSTextView property NSDictionary : a reference to current application’s NSDictionary property NSUnarchiver : a reference to current application’s NSUnarchiver property OSALanguage : a reference to current application’s OSALanguage property OSAScriptView : a reference to current application’s OSAScriptView property NSMutableArray : a reference to current application’s NSMutableArray property OSAScriptController : a reference to current application’s OSAScriptController property NSMutableDictionary : a reference to current application’s NSMutableDictionary property OSALanguageInstance : a reference to current application’s OSALanguageInstance property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding set postID to 3864 set {myUser, myPass} to getAcountData() of me set aURL to "http://piyocast.com/as/xmlrpc.php" set cocoaClassList to getCocoaPropListFromPost(aURL, postID, myUser, myPass) of me –> {"NSString", "NSBundle", "NSPredicate", "NSDictionary", "NSMutableArray", "NSMutableDictionary"} –指定Blogの指定IDの記事にURLリンクされているAppleScriptから、Cocoa Classのpropertyのみ取得する on getCocoaPropListFromPost(aURL, postID, myUser, myPass) –AppleScriptの構文色分け設定ファイルを読み込んで、重複色のチェックを実施 set cList to getAppleScriptSourceColors() of me set cRes to chkASLexicalFormatColorConfliction(cList) of me –構文色分けの重複色チェック if cRes = false then error "There is some duplicate(s) color among AppleScript’s lexical color settings" –WordPressの指定Post IDの記事を取得してリンクされているURLからURL Schemeでフィルタして、リンクされているAppleScriptのソースを取得 set aScheme to "applescript://" set sourceList to getASSouceLinkedInWordPressPost(postID, aURL, aScheme, myUser, myPass) of me –AppleScriptのソースをRAM上でコンパイル(構文確認)して、構文色分けしたRTFを取得。RTFの書式情報をparseしてattribute runsと同様のrecordを生成 –構文色分けをもとにproperty項目を抽出し、Cocoa Classに該当するもののみを抽出 set outList to {} repeat with i in sourceList set j to contents of i set anAttrStr to compileASandReturnAttributedString(j) of me set attrRes to getAttributeRunsFromAttrString(anAttrStr) of me set propNames to getPropertyNamesCocoaOnly(cList, attrRes) of me if propNames is not equal to {} then set outList to outList & propNames end if end repeat –1D Listのユニーク化(重複要素の排除) set aArray to NSArray’s arrayWithArray:outList set bArray to aArray’s valueForKeyPath:"@distinctUnionOfObjects.self" set bList to bArray as list of string or string –as anything return bList end getCocoaPropListFromPost –Property名称を取得する on getPropertyNamesCocoaOnly(cList, aRec) script spdHnd property aRec : {} end script set (aRec of spdHnd) to aRec set targAttr to contents of item 7 of cList –ハンドラあるいは変数 set tmpCoStr to ((redValue of targAttr) as string) & " " & ((greenValue of targAttr) as string) & " " & ((blueValue of targAttr) as string) set ontoColItem to contents of item 3 of cList –スクリプティング予約語(on/to) set ontoCoStr to ((redValue of ontoColItem) as string) & " " & ((greenValue of ontoColItem) as string) & " " & ((blueValue of ontoColItem) as string) –変数あるいはハンドラ名称をリストアップ(variables & handler) set tmp1Array to NSArray’s arrayWithArray:(aRec of spdHnd) set thePred0 to NSPredicate’s predicateWithFormat_("colorStr == %@", tmpCoStr) set dArray to (tmp1Array’s filteredArrayUsingPredicate:thePred0) as list –改行を含むデータをリストアップ(text data contains return) set thePred1 to NSPredicate’s predicateWithFormat_("stringVal CONTAINS %@", return) set eArray to ((tmp1Array’s filteredArrayUsingPredicate:thePred1)’s valueForKeyPath:"itemIndex") as list set the beginning of eArray to 0 –ハンドラ宣言部がTopに来る場合に備える –"property"(プロパティ宣言)の項目をリストアップ 文字と色で抽出 set thePred2 to NSPredicate’s predicateWithFormat_("stringVal == %@ && colorStr == %@ ", "property", ontoCoStr) set fArray to ((tmp1Array’s filteredArrayUsingPredicate:thePred2)’s valueForKeyPath:"itemIndex") as list set handlerList to {} –property ではじまるハンドラの抽出 repeat with i in eArray –改行を含むテキストのアイテム番号リスト set j to (contents of i) as integer repeat with ii in fArray –"on"の項目リスト set jj to (contents of ii) as integer set handlerStr to missing value if (j + 1) = jj then set handlerStr to stringVal of (item (jj + 2) of ((aRec of spdHnd) as list)) else if (j + 2) = jj then set handlerStr to stringVal of (item (jj + 2) of ((aRec of spdHnd) as list)) end if set tmpStr to repChar(handlerStr, "|", "") of me if tmpStr is not in {"error", missing value} and tmpStr is not in handlerList then –抽出したProperty宣言がCocoa Classのものかどうか判定 if searchClassInFrameworks(tmpStr) of me is not equal to false then set the end of handlerList to tmpStr end if end if end repeat end repeat return handlerList end getPropertyNamesCocoaOnly –RAM上にスクリプトエディタと同じ部品を組み立て(非表示)、AppleScriptのソーステキストからObjectを生成し、Attributed Stringデータを返す on compileASandReturnAttributedString(theSource as string) set targX to 1024 –View Width set targY to 2048 –View Height set osaCon to current application’s OSAScriptController’s alloc()’s init() set osaView to current application’s OSAScriptView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, targX, targY)) set resView to NSTextView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, targX, targY)) resView’s setRichText:true resView’s useAllLigatures:true osaCon’s setScriptView:osaView osaCon’s setLanguage:(OSALanguage’s languageForName:"AppleScript") osaCon’s setResultView:resView osaView’s setString:theSource osaCon’s compileScript:(missing value) –Compile(構文確認) set aRes to (osaView’s attributedString()) return aRes end compileASandReturnAttributedString –Attributed StringをDictionary化 on getAttributeRunsFromAttrString(theStyledText) script aSpd property styleList : {} end script set (styleList of aSpd) to {} —for output set thePureString to theStyledText’s |string|() –pure string from theStyledText set theLength to theStyledText’s |length|() set startIndex to 0 set itemCount to 1 repeat until (startIndex = theLength) set {theAtts, theRange} to theStyledText’s attributesAtIndex:startIndex longestEffectiveRange:(reference) inRange:{startIndex, theLength – startIndex} –String set aText to (thePureString’s substringWithRange:theRange) as string –Color set aColor to (theAtts’s valueForKeyPath:"NSColor") if aColor is not equal to missing value then set aSpace to aColor’s colorSpace() set aRed to (aColor’s redComponent()) * 255 set aGreen to (aColor’s greenComponent()) * 255 set aBlue to (aColor’s blueComponent()) * 255 set colList to {aRed as integer, aGreen as integer, aBlue as integer} –for comparison set colStrForFind to (aRed as integer as string) & " " & (aGreen as integer as string) & " " & (aBlue as integer as string) –for filtering else set colList to {0, 0, 0} set colStrForFind to "0 0 0" end if –Font set aFont to (theAtts’s valueForKeyPath:"NSFont") if aFont is not equal to missing value then set aDFontName to aFont’s displayName() set aDFontSize to aFont’s pointSize() end if set the end of (styleList of aSpd) to {stringVal:aText, colorStr:colStrForFind, colorVal:colList, fontName:aDFontName as string, fontSize:aDFontSize, itemIndex:itemCount} set startIndex to current application’s NSMaxRange(theRange) set itemCount to itemCount + 1 end repeat return (styleList of aSpd) end getAttributeRunsFromAttrString –指定クラスがいずれかのCocoa Frameworkに所属しているかを検索 on searchClassInFrameworks(aTarget) set aClass to current application’s NSClassFromString(aTarget) if aClass = missing value then return false set theComponenents to (NSBundle’s bundleForClass:aClass)’s bundleURL’s pathComponents() set thePred to NSPredicate’s predicateWithFormat:"pathExtension == ’framework’" set aRes to (theComponenents’s filteredArrayUsingPredicate:thePred)’s firstObject() as list of string or string return aRes end searchClassInFrameworks –指定Post IDのWordPress記事から、指定SchemeのURLを抽出し、AS Sourceをdecodeしてproperty行のみ抽出 on getASSouceLinkedInWordPressPost(postID, aURL, aScheme, myUser, myPass) –call xmlrpc命令に対するURLの間接指定を有効にするために、AppleScriptの構文解釈機能をダミーURLでだます using terms from application "http://piyocast.com/as/xmlrpc.php" –URLと判定されればなんでもいい tell application aURL set wRes to (call xmlrpc {method name:"wp.getPost", parameters:{"1", myUser, myPass, postID as string}}) end tell end using terms from set aBody to post_content of wRes –Blog本文 –記事中でリンクしているURLを取得し、指定のURL Schemeでフィルタする set urlList to filterURLLinksByScheme(aBody, aScheme) of me set propList to {} repeat with i in urlList set j to contents of i set urlRec to parseQueryDictFromURLString(j) of me set tmpScript to (urlRec’s |script|) as string –Get AppleScript Source set propList to propList & tmpScript end repeat return propList end getASSouceLinkedInWordPressPost on parseQueryDictFromURLString(aURLStr as string) if aURLStr = "" then error "No URL String" set aURL to |NSURL|’s URLWithString:aURLStr set aQuery to aURL’s query() –Get Query string part from URL if aQuery’s |length|() = 0 then return false set aDict to NSMutableDictionary’s alloc()’s init() set aParamList to (aQuery’s componentsSeparatedByString:"&") as list repeat with i in aParamList set j to contents of i if length of j > 0 then set tmpStr to (NSString’s stringWithString:j) set eList to (tmpStr’s componentsSeparatedByString:"=") set anElement to (eList’s firstObject()’s stringByReplacingPercentEscapesUsingEncoding:(NSUTF8StringEncoding)) set aValStr to (eList’s lastObject()’s stringByReplacingPercentEscapesUsingEncoding:(NSUTF8StringEncoding)) (aDict’s setObject:aValStr forKey:anElement) end if end repeat return aDict end parseQueryDictFromURLString –指定のHTML文字列から、Link URLを抽出し、schemeで再抽出する on filterURLLinksByScheme(aBody, aScheme) set conType to "text/html" –HTML文字列をいったんNSDataにしているのは、HTMLReader.frameworkの仕様のため set aData to (current application’s NSString’s stringWithString:aBody)’s dataUsingEncoding:(current application’s NSUTF8StringEncoding) set aHTML to current application’s HTMLDocument’s documentWithData:aData contentTypeHeader:conType set aTextArray to ((aHTML’s nodesMatchingSelector:"a")’s textContent) as list –リンク文字 set aLinkList to ((aHTML’s nodesMatchingSelector:"a")’s attributes’s valueForKeyPath:"href") as list –URL文字列 set outList to {} repeat with i in aLinkList set j to contents of i if j begins with aScheme then set the end of outList to j end if end repeat return outList end filterURLLinksByScheme –文字置換 on repChar(origText as string, targChar as string, repChar as string) set curDelim to AppleScript’s text item delimiters set AppleScript’s text item delimiters to targChar set tmpList to text items of origText set AppleScript’s text item delimiters to repChar set retText to tmpList as string set AppleScript’s text item delimiters to curDelim return retText end repChar –AppleScriptの構文色分けのカラー値をRGBで取得する on getAppleScriptSourceColors() — get the plist info as a dictionary set thePath to NSString’s stringWithString:"~/Library/Preferences/com.apple.applescript.plist" set thePath to thePath’s stringByExpandingTildeInPath() set theInfo to NSDictionary’s dictionaryWithContentsOfFile:thePath — extract relevant part and loop through set theArray to (theInfo’s valueForKey:"AppleScriptSourceAttributes") as list set colList to {} repeat with i from 1 to count of theArray set anEntry to item i of theArray set colorData to NSColor of anEntry set theColor to (NSUnarchiver’s unarchiveObjectWithData:colorData) set {rVal, gVal, bVal} to retColListFromNSColor(theColor, 255) of me set fontData to NSFont of anEntry set theFont to (NSUnarchiver’s unarchiveObjectWithData:fontData) set aFontName to theFont’s displayName() as text set aFontSize to theFont’s pointSize() set aColRec to {redValue:rVal, greenValue:gVal, blueValue:bVal, fontName:aFontName, fontSize:aFontSize} set the end of colList to aColRec end repeat return colList end getAppleScriptSourceColors –NSColorからRGBの値を取り出す on retColListFromNSColor(aCol, aMAX as integer) set aRed to round ((aCol’s redComponent()) * aMAX) rounding as taught in school set aGreen to round ((aCol’s greenComponent()) * aMAX) rounding as taught in school set aBlue to round ((aCol’s blueComponent()) * aMAX) rounding as taught in school if aRed > aMAX then set aRed to aMAX if aGreen > aMAX then set aGreen to aMAX if aBlue > aMAX then set aBlue to aMAX return {aRed, aGreen, aBlue} end retColListFromNSColor –AS書式で配色に重複がないかどうかチェック on chkASLexicalFormatColorConfliction(aList) set anArray to current application’s NSArray’s arrayWithArray:aList set bList to (anArray’s valueForKeyPath:"redValue.stringValue") as list set cList to (anArray’s valueForKeyPath:"greenValue.stringValue") as list set dList to (anArray’s valueForKeyPath:"blueValue.stringValue") as list set colStrList to {} repeat with i from 1 to (length of bList) set bItem to contents of item i of bList set cItem to contents of item i of cList set dItem to contents of item i of dList set the end of colStrList to bItem & " " & cItem & " " & dItem end repeat set aRes to returnDuplicatesOnly(colStrList) of me if aRes is equal to {} then return true –重複が存在しなかった場合 else return false –重複があった場合 end if end chkASLexicalFormatColorConfliction on returnDuplicatesOnly(aList as list) set aSet to current application’s NSCountedSet’s alloc()’s initWithArray:aList set bList to (aSet’s allObjects()) as list set dupList to {} repeat with i in bList set aRes to (aSet’s countForObject:i) if aRes > 1 then set the end of dupList to (contents of i) end if end repeat return dupList end returnDuplicatesOnly on getAcountData() return {"xxxxxxxx_xx", "XXXXXXXXXXXXXXXXXXXXXXXX"} –user name, password end getAcountData |