Opera プラグイン Obook の目次を HTML にする
Obook は設定されたディレクトリのルートに XML ファイル (obook.xml) を置いて、そのディレクトリ以下に収めたスクラップした Web サイトデータを管理しているようだ。
他のブラウザからもそれまで溜めたスクラップを見れるように、その XML ファイルをごにょごにょして HTML にする。
Nokogiri で SAX が思ったより気軽だった。
require 'nokogiri' require 'stringio' require 'optparse' class OBook def initialize @root = Folder.new @version = nil end attr_reader :root attr_reader :version def compile(visitor) visitor.apply_document{ traverse_for_visitor(visitor, root) } end def traverse_for_visitor(visitor, folder) folder.each do |ent| if ent.page? visitor.apply_page(ent) else visitor.apply_folder(ent){ traverse_for_visitor(visitor, ent) } end end end class Entry def initialize(table = {}) table.each do |key, val| instance_variable_set "@#{key.downcase}", val end end attr_reader :title attr_reader :date def page? not folder? end end class Folder < Entry def entries @entries ||= [] end def folder? true end def <<(ent) entries << ent end def each(&block) entries.each(&block) end end class Page < Entry attr_reader :url def folder? false end end class Document < Nokogiri::XML::SAX::Document def initialize(obook) @obook = obook @stack = [obook.root] end def start_element(name, attrs = []) case name when 'OBOOKS' then @obook.instance_eval{ @version = Hash[*attrs]['VERSION'] } when 'FOLDER' then @stack.push Folder.new(Hash[*attrs]) when 'PAGE' then @stack.last << Page.new(Hash[*attrs]) end end def end_element(name) case name when 'FOLDER' folder = @stack.pop @stack.last << folder end end end class HTMLVisitor def initialize(root_dir) @root_dir = root_dir @f = StringIO.new @indent_cache = {} end def string @f.string end def indent @indent_cache[@nest] ||= ' ' * @nest end def nesting(tag = nil) puts "<#{tag}>" if tag @nest += 1 ret = yield() @nest -= 1 puts "</#{tag}>" if tag ret end def puts(str) @f.puts "#{indent}#{str}" end def apply_document(&block) @nest = 0 nesting 'html' do nesting 'head' do puts "<title>OBook Index</title>" puts %Q`<style type="text/css">` puts "<!--" nesting do (<<-CSS).lines.each{|line| puts line.lstrip } body{ font-size : 90%; } h1{ border-left : solid 20px; border-bottom : solid 2px; padding-left : 0.5em; } a{ color : #000000; } CSS end puts "-->" puts "</style>" end nesting 'body' do puts "<h1>OBook Index</h1>" nesting 'ul', &block end end end def apply_folder(folder, &block) nesting 'li' do puts folder.title nesting 'ul', &block end end def apply_page(page) puts %Q`<li><a href="file:///#{File.join(@root_dir, page.date, 'index.htm')}" title="#{page.date} - #{page.url}">#{page.title}</a></li>` end end end def with_output(filename, alt, &block) filename ? File.open(filename, 'w', &block) : yield(alt) end input = nil output = nil ARGV.push '-o', 'obook.html' o = OptionParser.new o.on '-f', '--input=XML', 'specify OBook XML file' do |filename| input = filename end o.on '-o', '--output=DEST', 'specify output file' do |filename| output = filename end input ||= 'obook.xml' begin o.parse! ARGV obook = OBook.new visitor = OBook::HTMLVisitor.new(File.dirname(File.expand_path(input))) parser = Nokogiri::XML::SAX::Parser.new(OBook::Document.new(obook)) File.open(input){|f| parser.parse_io(f) } obook.compile(visitor) with_output(output, $stdout){|f| f.puts visitor.string } rescue => ex $stderr.puts "#{File.basename($0, '.rb')}: #{ex.message}" exit 1 end