シンタックスハイライトなHTMLに変換するオブジェクト指向AppleScript その2

...前の日からの続き

機能を細分化して汎用化する

  • その後、ハンドラやオブジェクトとして可能な限り機能を細分化して処理するようにしてみた。


--??は、インライン改行(option-L)にすべて置き換える
--半角の¥は、半角の\にすべて置き換える

property find_words : {"\t", "&", "<", ">"} --"\t" = tab
property puts_words : {" ", "&amp;", "&lt;", "&gt;"}

RTF's init()
set html to ""
repeat with i from 1 to RTF's everyText's number
set line_text to RTF's everyText's item i
set line_font to RTF's everyFont's item i
set line_size to RTF's everySize's item i
set line_color to RTF's everyColor's item i
repeat with j from 1 to line_text's number
set aText to line_text's item j
set aFont to line_font's item j
set aSize to line_size's item j
set aColor to line_color's item j
set html to html & text_or_tag(aText, aFont, aSize, aColor)
end repeat
end repeat

set html to "<pre>\n" & html & "\n</pre>" & return
set the clipboard to html



(*
RTFオブジェクト*)
--スクリプト実行時、最前面のドキュメントをAppleScriptから扱い易いオブジェクトに変換する
script RTF
global everyText, everyFont, everySize, everyColor
--初期化
on init()
--validate(検証)の順序大事
validate_presence_of_document()
parse()
validate_rtf()
validate_presence_of_text()
end init
--リッチテキスト情報を取得する(都度、メソッド呼び出しで処理すると、とっても遅くなったので、変数に代入することに)
on parse()
try
tell application frontmost_app()
tell document 1's paragraph
set everyText to (every attribute run)
set everyFont to (every attribute run)'s font
set everySize to (every attribute run)'s size
set everyColor to (every attribute run)'s color
end tell
end tell
end try
end parse
--documentの存在を検証
on validate_presence_of_document()
tell application frontmost_app()
try
if (count document) > 0 then return
end try
"ドキュメントがありません。"
display alert result giving up after 10
error
end tell
end validate_presence_of_document
--テキストの存在を検証
on validate_presence_of_text()
tell application frontmost_app()
try
if everyText's number > 0 then return
end try
"変換するテキストがありません。"
display alert result giving up after 10
error
end tell
end validate_presence_of_text
--変換できるリッチテキストかどうか検証
on validate_rtf()
tell application frontmost_app()
try
if everyText's number = everyFont's number and ??
everyText's number = everySize's number and ??
everyText's number = everyColor's number then return
end try
"リッチテキストでないため変換できません。"
display alert result giving up after 10
error
end tell
end validate_rtf
end script



(*
htmlに変換するハンドラ*)
--見えない文字はそのまま、見える文字はタグで囲って返す
on text_or_tag(aText, aFont, aSize, aColor)
--if reg("/^[\\t\\s\\r]+$/", originalText) then--処理が遅過ぎ
if is_invisible(aText) then
content_text(aText)
else
tag("span", content_text(aText), {"style", css(aFont, aSize, aColor)})
end if
end text_or_tag

--見えない文字の連続かどうか真偽値で返す(見えない:true)
on is_invisible(str)
str's item 1 & replace(str, str's item 1, "")
result is in {tab, space, return}
end is_invisible

--タグで囲って返す
--利用例:
-- tag("span", "ABC", {{"class","AppleScript"}, {"style","color:rgb(0,0,0);"}})
-- <span class="AppleScript" style="color:rgb(0,0,0);">ABC</span>
--ペアリストが一つだけの場合は下記でもOK
--利用例:
-- tag("span", "ABC", {"style","color:rgb(0,0,0);"})
-- <span style="color:rgb(0,0,0);">ABC</span>
on tag(tag_name, str, attr_pair_list)
"<" & tag_name & attr_text(double_list_from(attr_pair_list)) & ">" & str & "</" & tag_name & ">"
end tag

--二重のリストに補正する
--本来、リストの中にリストを指定する想定なのだが...
-- {{"class","AppleScript"}, {"style","color:rgb(0,0,0);"}}
--リスト一つだけでは以下のように書いてしまいがち
-- {"class","AppleScript"}
--そのような状況で、本来の二重リストに修正する
-- {{"class","AppleScript"}}
on double_list_from(aList)
if aList's item 1's class is list then
aList
else
{aList}
end if
end double_list_from

--ペアリストから属性を指定するテキストを返す
--ペアリストとは{キー,値}のリストから構成されるリストを想定している
--{{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}}
--利用例:
-- attr_text({{"class","AppleScript"}, {"style","color:rgb(0,0,0);"}})
-- " class=\"AppleScript\" style=\"color:rgb(0,0,0);\""
on attr_text(pair_list)
set aText to ""
repeat with pair in pair_list
set aText to aText & space & pair's item 1 & "=\"" & pair's item 2 & "\""
end repeat
end attr_text

--必要な置き換えをしたテキストを返す
on content_text(str)
every_replace(str, find_words, puts_words)
end content_text

--インラインスタイルを返す
on css(aFont, aSize, aColor)
css_color(aColor) & css_font_size(aSize) & css_font_family(aFont) -- & css_font_weight(aFont)
end css

--フォントの色の設定コードを返す color:rgb(255,255,255);
on css_color(aColor)
"color:" & rgb_color(aColor) & ";"
end css_color

--フォントサイズの設定コードを返す font-size:12px;
on css_font_size(aSize)
"font-size:" & aSize & "px;"
end css_font_size

--フォントの太さの設定コードを返す font-weight:bold;
on css_font_weight(aFont)
if "bold" is in aFont then
"font-weight:bold;"
end if
end css_font_weight

--フォントの種類の設定コードを返す font-family:Osaka
on css_font_family(aFont)
"font-family:" & aFont
end css_font_family

--RGB指定の文字列を返す
--利用例:
--rgb_color({65535, 65535, 65535})
-- 結果:"rgb(255,255,255)"
on rgb_color(color_list)
set R to (color_list's item 1) div 256
set G to (color_list's item 2) div 256
set B to (color_list's item 3) div 256
"rgb(" & R & "," & G & "," & B & ")"
end rgb_color



(*
以下、依存するコードをライブラリ(_lib.scpt、_gui.scpt)からコピーした*)
--最前面のアプリケーション名(拡張子なし)を取得する
on frontmost_app()
--short name of (info for (path to frontmost application)) -- short name属性がない場合、missing valueが返ってくる
--name of (path to frontmost application) --拡張子が付属してしまう。"Script Editor.app"
tell application "System Events"
set name_list to processes's name whose frontmost is true
name_list's first item
end tell
end frontmost_app

--sourceTextをseparatorでリストに変換する
--split("1,2,3,4", ",")
-- 結果:{"1", "2", "3", "4"}
on split(sourceText, separator)
if sourceText = "" then return {}
set oldDelimiters to AppleScript's text item delimiters
set AppleScript's text item delimiters to {separator}
set theList to text items of sourceText
set AppleScript's text item delimiters to oldDelimiters
return theList
end split

--sourceListをseparatorで区切ったテキストに変換する
--join({"1", "2", "3", "4"}, ",")
-- 結果:"1,2,3,4"
on join(sourceList, separator)
set oldDelimiters to AppleScript's text item delimiters
set AppleScript's text item delimiters to {separator}
set theText to sourceList as text
set AppleScript's text item delimiters to oldDelimiters
return theText
end join

--sourceText中の全てのtext1をtext2に置き換える
--replace("abcdefg", "bc", "_bc_")
-- 結果:"a_bc_defg"
on replace(sourceText, text1, text2)
join(split(sourceText, text1), text2)
end replace

--sourceText中の全てのlist1をlist2に置き換える
--every_replace("abcdefg", {"bc", "e", "g"}, {"_bc_", "_e_", "_g_"})
-- 結果:"a_bc_d_e_f_g_"
on every_replace(sourceText, list1, list2)
repeat with i from 1 to list1's number
set sourceText to replace(sourceText, list1's item i, list2's item i)
end repeat
end every_replace

これで、任意のRTF書類をシンタックスハイライトなHTMLに変換できるようになった!

  • HTMLに変換したいリッチテキストなウィンドウをアクティブにして上記スクリプトを実行すれば、
  • クリップボードにpreタグで囲ったシンタックスハイライトなHTMLを生成する。
  • 自分の環境ではスクリプトエディタやテキストエディットのウィンドウに表示される内容を変換できた。
  • その効果は、この日記のAppleScriptコードがカラフルになったこと。
  • フォント、サイズ、カラー以外のRTF情報は変換してないので、対応していない情報が多く含まれると、書式が崩れているように見えてしまうかも。

所感

  • 機能をうまく細分化できれば、ほとんどのハンドラ(メソッド)はたった数行のシンプルなコードの集まりになってくるのだと思う。
  • それらのシンプルなハンドラが、さらにシンプルなハンドラを呼び出し...の連携によって、最終的に複雑な目的を達成するのが理想。
  • おそらく、一つのハンドラに10行以上のコードが書かれていれば、そこには複数の機能が混ざってしまっているのだと思う。
  • ハンドラ(メソッド)やオブジェクトの命名が良いと、まるで自然言語のように読める。(気がする)

次の日に続く...