ho.rb
#! ruby -Ks
#ho 文書をhtmlに書き出す
#
#
#
#2008/03/03〜prototype
#2008/03/09〜module class化する。。。断念(^^;)
#2008/03/16 UNESC の自動化他
#2008/03/26 出力ファイルの複数化に暫定対応他
#2008/03/28 link_name 関連整理,doc_conf 整理,ERBのbinding処理追加,その他整理
#2008/05/30 @gsub_list (ハッシュ)によるテキスト置換を追加
#2008/09/28 クラス化実行により、全面書き換え
#2008/09/30 各種微調整
#2008/11/12 プロジェクトフォルダに %conf% %style% %plugin% が無い場合、カレントアイテムからも読み込むように修正
#2011/01/06 コメント類の整理
#2012/02/23 typo修正
#2012/03/23 CSS見直しに伴い各種微調整
#eval_start
require "tpz"
include Tpz
require 'erb'
org_path = File.dirname( __FILE__ )
$:.unshift( org_path )
require 'sunokodoc'
# デバッグ表示フラグ
Debug = true
#Debug = false
###Utility
#----html エスケープ処理
#ESCに要素を追加すれば、自動的に UNESC も生成される
#
#require "erb"
#include ERB::Util
#これで、実はできたりする。しかも、alias してあって、
# h hoge
#で使えたり(^^;)
#
ESC = {
'&' => '&',
'<' => '<',
'>' => '>',
'"' => '"'
}
UNESC = Hash.new
for i in 0 .. ESC.size
UNESC.store(ESC.values[i],ESC.keys[i])
end
def hreg( h ) #h:hash の keys 又は values の正規表現化
reg = ''
h.each do |s|
reg << "(#{s})|" #単純に()でグルーピング
end
return reg.gsub(/\|$/ , '') #最後の余分な | を削除
end
def escape(str) #エスケープ
str.gsub( Regexp.new( hreg( ESC.keys) ) ) {|s| ESC[s] }
end
def unescape(str) #アンエスケープ
str.gsub( Regexp.new( hreg( ESC.values) ) ) {|s| UNESC[s] }
end
###
#既存クラス拡張
###
class String
$KCODE = 's'
require 'nkf'
require 'jcode'
def up # 半角アルファベット→すべて大文字
self.upcase
end
def down # 半角アルファベット→すべて小文字
self.downcase
end
def swap # 半角アルファベット→大文字小文字反転
self.swapcase
end
def za # alphabet 半角アルファベット→全角
self.tr("a-zA-Z","a-zA-Z")
end
def ha # alphabet全角アルファベット→半角
self.tr("a-zA-Z","a-zA-Z")
end
def zn # numeric 半角数字→全角
self.tr("0-9","0-9")
end
def hn # numeric 全角数字→半角
self.tr("0-9","0-9")
end
def zk # kana 半角カナ→全角カナ
NKF::nkf( '-SsX -m0', self )
end
def hk # kana 全角カナ→半角カナ
str = kanabreak(self)
zenkana = "。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゛゜"
a = zenkana.split(//)
kanahash = {}
a.each_index {|i| kanahash[a[i]] = i + 0xA1 }
str.gsub!(/[#{zenkana}]/) { |c| kanahash[c].chr }
end
def zs # symbol 半角記号類→全角
hs = '!"#$%&\'()*+,-./:;<=>?@[]^_`{|}~'
zs = '!”#$%&’()*+,−./:;<=>?@[]^_`{|}〜'
self.tr('\\','¥').tr(hs,zs)
end
def hs # symbol 全角記号類→半角
hs = '!"#$%&\'()*+,-./:;<=>?@[]^_`{|}~'
zs = '!”#$%&’()*+,−./:;<=>?@[]^_`{|}〜'
self.tr('¥','\\').tr(zs,hs)
end
private
def kanabreak(str)
daku = "ガギグゲゴザジズゼゾダヂヅデドバビブベボヴ"
daku2 = "カキクケコサシスセソタチツテトハヒフヘホウ"
handaku = "パピプペポ"
handaku2 = "ハヒフヘホ"
str.gsub!(/[#{daku}]/) {|c| c + "゛"}
str.gsub!(/[#{handaku}]/) {|c| c + "゜"}
str.tr!(daku,daku2)
str.tr!(handaku,handaku2)
str.tr!("ヵヶヰヱヮ","カケイエワ")
return str
end
end
class TpzItem
#TpzItemクラスを拡張する
def get_mid_item(message_id)
# Message_id なアイテムを取得する。
item = self
while item do
if item.message_id == message_id then
return item
end
item = item.next
end
end
def get_title_item(title)
# title なアイテムを取得する。
item = self
while item do
if item.title == title then
return item
end
item = item.next
end
end
def get_title_s_item(title)
# 最初のスペースまでを有効タイトルとしたアイテムを取得する。
item = self
while item do
if item.title.strip.split(/\s+/)[0] == title then
return item
end
item = item.next
end
end
def get_title_doc(title)
# title な文書を取得する。
item = self
item.documents.each do |doc|
if doc.title == title then
return doc
end
end
end
end
class TpzItemDocument
# def initialize
# self.initialize
# @doc_type = ''
# end
def doc_type= (s)
# 文書のタイプを決定する
@doc_type = s
end
def doc_type
# 文書のタイプを決定する
if @doc_type == nil then
case self.title
when /^\%.*\%$/i
type = 'pre'
when /pre/i
type = 'pre'
when /wiki/i
type = 'wiki'
when /hiki/i
type = 'wiki'
when /html/i
type = 'html'
when /SunokoDoc/i
type = 'SunokoDoc'
else
type = 'SunokoDoc'
end
else
type = @doc_type
end
return type
end
def translate_text( type = @doc_type )
# doc_type に応じたテキストに変換する
text = self.text
case type
when ''
text = text
when 'html'
text = text
when 'pre'
text = "<pre>\n#{escape(text)}\n</pre>\n"
when 'wiki'
text = SunokoDoc.new( text ).to_html #SunokoDoc の標準はHikiDoc
when 'SunokoDoc' #<SunokoDoc>〜</SunokoDoc>を省略させたいので
text = "<SunokoDoc>\n#{text}\n</SunokoDoc>\n"
text = SunokoDoc.new( text ).to_html
else
text = SunokoDoc.new( text ).to_html
end
return text
end
end
class Config
def initialize
load
set_instance_variables
end
def plugin(plugin_src)
instance_eval(plugin_src )
reconf("%doc_conf%", tpz_current_project) #%doc_conf% は plugin をロードしてからとする
reconf("%add_header_footer%", tpz_current_project) #%add_header_footer% は plugin をロードしてからとする
end
############################################################
#システム変数を定義する
#文書毎に設定される
def system_para(doc)
#文書中に指定できる各種情報を定義する
@Doc = doc #この文書そのもの
@DocTitle = doc.title #この文書の文書名
@DocIndex = doc.index #この文書の文書番号(0〜)
@DocText = doc.text #この文書の生テキスト
docs = doc.parent.documents
next_index = doc.index + 1
prev_index = doc.index - 1
if next_index < docs.size then
@NextDocTitle = docs[next_index].title #次の文書の文書名
@NextDocIndex = docs[next_index].index #次の文書の文書番号(0〜)
else
@NextDocTitle = doc.title
@NextDocIndex = doc.index
end
if prev_index >= 0 then
@PrevDocTitle = docs[prev_index].title #前の文書の文書名
@PrevDocIndex = docs[prev_index].index #前の文書の文書番号(0〜)
else
@PrevDocTitle = doc.title
@PrevDocIndex = doc.index
end
item = doc.parent
@Item = item #この文書があるアイテムそのもの
if item == nil then
#何もしない
else
@ItemSubject = item.title #アイテムの件名
@ItemMessageId = item.message_id #アイテムのメッセージID
if item.parent == nil then
@ParentSubject =item.title #親アイテムの件名
@ParentMessageId =item.message_id #親アイテムのメッセージID
else
@ParentSubject =item.parent.title #親アイテムの件名
@ParentMessageId =item.parent.message_id #親アイテムのメッセージID
end
@ProjectSubject = item.project.title #プロジェクトの件名
@ProjectMessageId = item.project.message_id #プロジェクトのメッセージID
if item.next == nil then
@NextItemSubject = item.title
@NextItemMessageId = item.message_id
#@NextItemWikiLink = SunokoDoc.new("[[#{@NextItemSubject}|#link(#{@NextItemMessageId})]]").to_html
#@NextItemWikiLink = @NextItemWikiLink.gsub(/(<p>)(.*?)(<\/p>$)/m){ ($2) }
else
@NextItemSubject = item.next.title #次のアイテムの件名
@NextItemMessageId = item.next.message_id #次のアイテムのメッセージID
#@NextItemWikiLink = SunokoDoc.new("[[#{@NextItemSubject}|#link(#{@NextItemMessageId})]]").to_html
#@NextItemWikiLink = @NextItemWikiLink.gsub(/(<p>)(.*?)(<\/p>$)/m){ ($2) }
end
if item.prev == nil then
@PrevItemSubject = item.title
@PrevItemMessageId = item.message_id
#@PrevItemWikiLink = SunokoDoc.new("[[#{@PrevItemSubject}|#link(#{@PrevItemMessageId})]]").to_html
#@PrevItemWikiLink = @PrevItemWikiLink.gsub(/(<p>)(.*?)(<\/p>$)/m){ ($2) }
else
@PrevItemSubject = item.prev.title #前のアイテムの件名
@PrevItemMessageId = item.prev.message_id #前のアイテムのメッセージID
#@PrevItemWikiLink = SunokoDoc.new("[[#{@PrevItemSubject}|#link(#{@PrevItemMessageId})]]").to_html
#@PrevItemWikiLink = @PrevItemWikiLink.gsub(/(<p>)(.*?)(<\/p>$)/m){ ($2) }
end
end
end
# %???% な文書をパラメータとする。アイテムごとに再定義可能。
# 自分のアイテムにパラメータがない場合は、親に向かって算出する
def reconf( doc_title, item=tpz_current_item )
if /%.*%$/ !~ doc_title then
p "パラメータ文書名が不適切です" if Debug
return
end
src = ''
while item do #%??%のあるアイテムを親に向かって算出する
item_src = %Q|tpz_current_project.get_mid_item("#{item.message_id}")|
begin
src << %Q|eval(%q!#{item_src}.get_title_doc(doc_title).text!)\n| if item.get_title_doc(doc_title).text
rescue
end
if item.project? then
item = nil
else
item = item.parent
end
end
srces = src.split(/\n/).reverse #下位のアイテムでの %???% 優先とするので逆順にする
text = ''
srces.each do |s| #各ソースを実行し、%???% テキストを算出する
text << "#{eval(s)}"
end
eval(text) #最終的なパラメータテキストをevalしパラメータ設定
set_instance_variables
end
private
def set_instance_variables
instance_variables.each do |v|
v.sub!( /@/, '' )
instance_eval( <<-SRC
def #{v}
@#{v}
end
def #{v}=(p)
@#{v} = p
end
SRC
)
end
end
def load
#プロジェクト固有のパラメータ(必須)
#最低限必要なパラメータをここで定義しておく
#上書き可能なので、 %conf% 等で再定義しても問題ない
@header = ""
@footer = ""
@docprint_all = false
@folderprint_all = false
@non_print_docs = []
@pre_doc_list = {}
begin
eval( tpz_current_project.get_title_doc( "%style%" ).text )
rescue
begin
eval( tpz_current_item.get_title_doc( "%style%" ).text )
rescue
end
end
begin
eval( tpz_current_project.get_title_doc( "%conf%" ).text )
rescue
begin
eval( tpz_current_item.get_title_doc( "%conf%" ).text )
rescue
end
end
#item毎に再定義可能なパラメータ。初期値をセット
@item_header = ""
@item_footer = ""
@doc_header = ""
@doc_footer = ""
@gsub_list = {}
#出力ファイル名のイニシャル定義
###ファイル名関係のパラメータ
#基本的には各アイテムの %output_file_conf% にあることを期待している
#ディレクトリのパラメータも用意してあるが、実際のリンク作成( toc プラグイン)は File.basename で実施
# →フルパスだと、ローカルフォルダ、サーバーフォルダ等の変換も必要。
# →css ファイル等も考慮しなければならない。
# →css は、あまり散在させたくないので、あくまでも、
# htmlファイルのあるディレクトリ下の css ディレクトリにあると仮定する
#
#ファイル名の自動作成も検討したが、結局、自由に設定できないことから、
#それならば、各アイテムごとに設定できるように変更した。
@of_full = tpz_current_project.project_filename
@of_dir = File.dirname( @of_full ) + '\\' #@of_dir を定義すれば、保存ディレクトリも指定可能ではある。。。
@of_base = File.basename( @of_full )
@of_ext = File.extname( @of_full )
@of = @of_base.gsub(/#{@of_ext}$/, '')
@of_name = @of_base.gsub(/#{@of_ext}$/, '') + '.html' #%output_file_conf% にて 拡張子付で定義すること
#一応再定義しておく
reconf("%output_file_conf%",tpz_current_project)
#reconf("%doc_conf%", tpz_current_item) #%doc_conf% は plugin をロードしてからとする
system_para(tpz_current_project.documents[0])
set_of_id
end
###各アイテム毎の出力ファイル名を、Hash で持つ
#@of_id ハッシュ
# keys = 'アイテムmessage_id'
# values = 'ファイル名(フルパス)'
#@of_uniq_array ファイル配列
def set_of_id
@of_id = Hash.new
item = tpz_current_project
while item do
reconf( "%output_file_conf%", item )
@output_file_name = @of_dir + @of_name #実際の保存ファイル名(フルパス)
@of_id[item.message_id] = @output_file_name
item = item.next
end
@of_uniq_array = @of_id.values.uniq.sort
@of_uniq_array.each do |f|
print "【ファイル名:#{f}】\n" if Debug
end
end
end
class Plugin
###プラグインをロードする
#プラグインパスは固定とする /plugin/
def initialize( conf )
@conf = conf
#@conf のパラメータを同じく定義する
@conf.instance_variables.each do |v|
v.sub!( /@/, '' )
instance_eval( <<-SRC
@#{v} = @conf.#{v} #インスタンス変数セット
SRC
)
end
read_plugin
load_plugin
end
def src
@plugin_src
end
private
def load_plugin
instance_eval( @plugin_src )
end
def read_plugin
@plugin_src = ''
begin
plugin_path = File.dirname( __FILE__ ) + "/plugin/*.rb"
print "plugin_path = #{plugin_path}\n" if Debug
Dir::glob( plugin_path ).sort.each do |file|
open( file ) do |src|
print "plugin file = #{file}\n" if Debug
@plugin_src << src.read
end
end
rescue Exception
end
begin
@plugin_src << tpz_current_project.get_title_doc( "%plugin%" ).text
rescue
begin
@plugin_src << tpz_current_item.get_title_doc( "%plugin%" ).text
rescue
end
end
end
end
#TaskPrize の ドキュメントクラスを対象にする
class Ho
def initialize( conf, plugin, item=tpz_current_project)
@conf = conf
@plugin = plugin
@item = item
@doc = @item.documents[0] #一応設定しておく
instance_eval( @plugin.src )
set_para( @doc )
end
def set_para( doc )
@doc = doc
@conf.system_para(@doc)
@conf.reconf( "%doc_conf%", @doc.parent )
set_instance_variables
@doc_type = @pre_doc_list[@doc.title] # %conf%で決定されている場合優先
if @doc_type == nil
@doc_type = @doc.doc_type # 決定されていない場合、文書名規則で決定する
end
end
def init_conf(item)
@conf.reconf( "%conf%", item )
set_instance_variables
end
def doc_html( doc )
@doc = doc
set_para( @doc )
r = ''
html = ''
html << %Q|<div class="doc" >\n|
html << "#{@doc_header}\n"
title = @doc.title.gsub(/<(.*?)>/,'') #タイトル内の<?>を削除
if title == ''
#r << %Q|<h1 class="doc_title">文書(#{@doc.index + 1})</h1>\n|
else
r << %Q|<h1 class="doc_title">#{escape( t2html( title ) )}</h1>\n|
end
r << %Q|#{@doc.translate_text(@doc_type)}\n|
html << "#{doc_name( r2html( r ,binding ) )}\n"
#@doc_footer でテキスト内容によるマクロ展開をしているものもあるかもしれないので、再度設定する。
@conf.reconf( "%doc_conf%", @doc.parent )
html << "#{@conf.doc_footer}\n" #初期化が終わったあとなので、@doc_footerには反映していない。よって、@conf.doc_footer とする。
html << %Q|</div>\n|
end
def item_html( item )
@item = item
set_para( @item.documents[0] )
# init_conf( tpz_current_project )
r = ""
r << %Q|<a name="#{link_name(@item.message_id)}"></a>\n|
r << %Q|<div class="item" >\n#{@item_header}\n|
r << %Q|<h1 class="item_title">#{escape(t2html(@item.title))}</h1>\n|
if @docprint_all then
@item.documents.each do |doc|
if @non_print_docs.index(doc.title) == nil then
r << %Q|<a name="#{link_name(@item.message_id, doc.index)}"></a>\n|
r << %Q|#{doc_html(doc)}\n|
end
end
else
r << %Q|#{doc_html(@item.documents[0])}\n| #index=0 固定
end
r = %Q|#{ r }\n#{@item_footer}\n</div>| #div 要調整
end
private
def set_instance_variables
#@conf のパラメータを同じく定義する
#特異メソッドを定義する必要がなければ、def は必要ない
@conf.instance_variables.each do |v|
v.sub!( /@/, '' )
instance_eval( <<-SRC
@#{v} = @conf.#{v} #インスタンス変数セット
def #{v} #特異メソッド定義(必要ない?)
@#{v}
end
def #{v}=(p)
@#{v} = p
end
SRC
)
end
end
end
#####################
#main処理
#####################
###itemのメッセージ番号よりファイル名を取得し、
#インスタンス作成
conf = Config.new
plugin = Plugin.new(conf)
conf.plugin(plugin.src)
h = Ho.new( conf, plugin)
#初期化をかねて @header を配列に入れる
#この処理あまりよろしくない???
html = ''
html_array = Array.new( h.of_uniq_array.size ){''}
html_array.each_with_index do |s, index|
file_title = File.basename(h.of_uniq_array[index]).gsub(/#{File.extname(h.of_uniq_array[index])}$/, '')
print "【h.of_uniq_array[index] = #{h.of_uniq_array[index]}】\n"
print "【file_title = #{file_title}】\n"
print "【#{h.header}】\n"
html_array[index] = s + ERB.new(h.header).result(binding)
end
items = tpz_selected_items
if tpz_focus_pain != 3 #エディットウィンドウ以外の場合選択アイテムを実行
items.each do |item|
if h.folderprint_all == true then
top_level = item.level
while item do #フォルダの場合、下部のアイテムも出力する
file_name = h.of_id[item.message_id]
index = h.of_uniq_array.index( file_name )
html = html_array[index]
html << %Q|#{h.item_html(item)}\n|
html_array[index] = html
item = item.next
if item and item.level <= top_level then
item = nil
end
end
else
file_name = h.of_id[item.message_id]
index = h.of_uniq_array.index( file_name )
html = html_array[index]
html << %Q|#{h.item_html(item)}\n|
html_array[index] = html
end
end
else #エディットウィンドウの場合、その文書のみ
file_name = h.of_id[tpz_current_item.message_id]
index = h.of_uniq_array.index( file_name )
html = html_array[index]
html << %Q|<a name="#{h.link_name(tpz_current_item.message_id, tpz_current_document.index)}"></a>\n|
html << %Q|#{h.doc_html(tpz_current_document)}\n|
html_array[index] = html
end
html_array.each_with_index do |s, index|
html_array[index] = s + ERB.new(h.footer).result(binding)
end
#@of_uniq_array ごとに出力する必要有り
h.of_uniq_array.each_with_index do |f, index|
out_f = open(f,"w")
out_f.write( html_array[index].zk) #半角カナは全角カナに強制変換
out_f.close
end
#eval_end