作りたかったはてな記法
自分用なので、僕がよく使う記法だけつくった
- H3/H4/H5が変換できる
- スーパーPreが変換できる
- Preが変換できる
- 表組みが変換出来る
- リストが変換できる
- http:// でリンクが作れる
- image タグを作れる。
Rubyで書いたはてな記法パーサー
なんとなく思いつきで書き始めて、新快速の中でちょこちょこ書いた。やつを修正してエントリにしました。
思いつくままに書いたので、今読むと意味不明。なんで、IOで実装してる?なにしてんだ。オレ。
それなりに変換できるので、エントリにしておきました。*1
hatena_parser.rb
#!/usr/bin/env ruby ##はてなダイアリ記法をparseする。 #テキストを読み込む #一行ずつ処理をする。 #全ての記法の記法チェッカを通す。 #記法に処理は委譲する。 #ブロック要素の場合は行を読み進みを認める。 class WikiPragma def initialize () end def match?(line) line =~ @syntax end def replace line line.sub @syntax, @replace end def to_html io point = io.pos io.gets return unless $_ if self.match? $_ return self.replace $_ else io.seek(-($_.length),IO::SEEK_CUR) "" end end end class Oneline < WikiPragma def to_html io super end end class H3 < Oneline def initialize () @syntax = %r'^\*{1}([^\*].*)$' @replace = '<h3>\1</h3>' end def to_html io io.gets return unless $_ if self.match? $_ return self.replace $_ else io.seek(-($_.length),IO::SEEK_CUR) "" end end end class H4 < Oneline def initialize () @syntax = %r'^\*{2}([^\*].*)$' @replace = '<h4>\1</h4>' end end class H5 < Oneline def initialize () @syntax = %r'^\*{3}([^\*].*)$' @replace = '<h5>\1</h5>' end end class HName < Oneline def initialize () @syntax = %r'^\*{1}([^\*].*)\*{1}(.+)$' @replace = '<h3>\1</h3><a href="\1">' end end class Http < Oneline def initialize () @syntax = %r'(https?://[^\s]+)' @replace = '<a href="\1">\1</a>' end def match?(line) line =~ @syntax and not (line =~ %r'\[https?://[^\s]+') end end class ExHttp < Oneline def initialize () @syntax = %r'\[(https?://[^:]+)([^\]]*)\]' @replace = '<a href="\1">\1</a>' end def replace text text =~ @syntax uri = $1 options = $2 if options =~ %r"title=([^:]+)" textnode = $1 elsif options =~ %r"title=?" textnode = self.page_title uri elsif options =~ %r"image" width = $1 if options =~ %r":w([0-9]+)" height = $1 if options =~ %r":h([0-9]+)" textnode = "<img src=\"#{uri}\" " textnode = textnode + "width=\"#{width}\" " if width textnode = textnode + "height=\"#{height}\" " if height textnode = textnode + "/>" end textnode = uri unless textnode "<a href=\"#{uri}\">#{textnode}</a>" end def page_title url require 'open-uri' require 'kconv' title = 'タイトル不明' if open(url).read =~ %r'<title>([^<]+)</title>' title = $1.toutf8 end title end end class MultiLine < WikiPragma def match? line self.match_open? line end def match_close? line (line =~ @syntax_close) != nil end def match_open? line (line =~ @syntax_open ) != nil end def read_block io text = "" while(io.gets and not self.match_close? $_ ) text = text + $_ end io.seek(-($_.length),IO::SEEK_CUR) if $_ and @no_close_syntax text end def to_html io point = io.pos io.gets return unless $_ io.seek(-($_.length),IO::SEEK_CUR) text = self.read_block io if self.match? $_ #io.seek(point) return self.replace text if text end end class Pre < MultiLine def initialize super @syntax_open = %r'^>\|$' @syntax_close = %r'^\|<$' @no_close_syntax = (@syntax_open == @syntax_close) end def replace str str.gsub!(@syntax_open , '') str.gsub!(@syntax_close , '') str = "<pre>#{str}</pre>\n" end end class SuperPre < Pre def initialize super @syntax_open = %r'^>\|\|$' @syntax_close = %r'^\|\|<$' @no_close_syntax = (@syntax_open == @syntax_close) end def replace str str = "<pre>#{self.escape_html_special_chars(str)}</pre>\n" end def escape_html_special_chars str str.gsub!(@syntax_open , '') str.gsub!(@syntax_close , '') str.gsub!(%r'&', '&') str.gsub!(%r'<', '<') str.gsub!(%r'>', '>') str.gsub!(%r'"', '"e;') str end end class Table < MultiLine def initialize super @syntax = %r'^\|\*?(.+)\|$' @syntax_close = @syntax @syntax_open = @syntax @no_close_syntax = (@syntax_open == @syntax_close) end def match_close? line (line =~ @syntax) == nil end def read_block io text = super io.seek(-($_.length),IO::SEEK_CUR) if $_ text end def replace str str.gsub! %r'\|$', '</tr>' str.gsub! %r'\|\*([^\|<]+)', '<th>\1</th>' str.gsub! %r'\|([^\*|\||<]+)', '<td>\1</td>' str.gsub! %r'^', '<tr>' str = "<table>\n#{str}</table>\n" end end class List < MultiLine def initialize super @syntax = %r'^[-\+](.+)$' @syntax_close = @syntax @syntax_open = @syntax @no_close_syntax = (@syntax_open == @syntax_close) end def match_close? line (line =~ @syntax_close) == nil end def read_block io text = "" while(io.gets and not self.match_close? $_ ) text = text + $_ end io.seek(-($_.length),IO::SEEK_CUR) if $_ text end def replace lines_text tag = "ul" if (lines_text =~ %r'^-' ) == 0 tag = "ol" if (lines_text =~ %r'^\+') == 0 lines_text.gsub!(@syntax ,'\1' ) lines_text = lines_text + "\n" str = StringIO.new(lines_text) text = "" list = self.class.new while str.gets if list.match? $_ then str.seek(-($_.length),IO::SEEK_CUR) ret = list.to_html str text = text + ret else text = text + $_.gsub(%r'^(.+)$', '<li>\1</li>') end end text.chomp! text = "<#{tag}>\n#{text}</#{tag}>\n" end end class DT < MultiLine def initialize super @syntax = %r'^:([^:]+):(.+)$' @syntax_close = @syntax @syntax_open = @syntax @no_close_syntax = (@syntax_open == @syntax_close) end def match_close? line (line =~ @syntax) == nil end def replace str str.gsub! @syntax, '<dt>\1</dt><dt>\2</dt>' str = "<dl>\n#{str}</dl>\n" end end class Paragraph < Oneline def initialize super end def match? line true end def replace str "<p>#{str}</p>\n" end end class Echo < Oneline def match? line true end def to_html io io.gets end end class HatedaSyntax def initialize require 'stringio' @pragmas_multiline =[] @pragmas_multiline.push Pre.new @pragmas_multiline.push SuperPre.new @pragmas_multiline.push List.new @pragmas_multiline.push Table.new @pragmas_multiline.push DT.new @pragmas_oneline =[] @pragmas_oneline.push H5.new @pragmas_oneline.push H4.new @pragmas_oneline.push HName.new @pragmas_oneline.push H3.new @pragmas_inline =[] @pragmas_inline.push Http.new @pragmas_inline.push ExHttp.new ## @pragmas_no_match = Paragraph.new end def to_html io io = @pragmas_inline.inject(io){ |sio,e| html = StringIO.new() while sio.gets if e.match? $_ then sio.seek(-($_.length),IO::SEEK_CUR); $_ = e.to_html sio end html.puts $_ end html.rewind sio = html } html = StringIO.new() while io.gets matched = (@pragmas_multiline+ @pragmas_oneline).find{|e| e.match? $_ } matched = @pragmas_no_match unless matched io.seek(-($_.length),IO::SEEK_CUR) puts matched html.puts matched.to_html io end html.rewind html.read end def parse str sio = StringIO.new str self.to_html sio end end if __FILE__ == $0 text =<<EOS [http://f.hatena.ne.jp:title] EOS hateda = HatedaSyntax.new ret = hateda.parse text puts ret end