それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

はてなダイアリ記法パーサーっぽいもの

はてな記法を他でも使いたい。

はてなダイアリ記法を他でも使いたいと思いました。HTML変換は正規表現にすればいいのでちょっち作ってみようと思って始めてみた。

作りたかったはてな記法

自分用なので、僕がよく使う記法だけつくった

  • H3/H4/H5が変換できる
  • スーパーPreが変換できる
  • Preが変換できる
  • 表組みが変換出来る
  • リストが変換できる
  • http:// でリンクが作れる
  • image タグを作れる。

Rubyで書いたはてな記法パーサー

なんとなく思いつきで書き始めて、新快速の中でちょこちょこ書いた。やつを修正してエントリにしました。
思いつくままに書いたので、今読むと意味不明。なんで、IOで実装してる?なにしてんだ。オレ。

それなりに変換できるので、エントリにしておきました。*1

作ったはてな記法変換
記法 出来るもの 実装?
http記法 http:// .. :title :image
見出し *hogehoge
小見出し **hogehoge
小小見出し ***hogehoge
名前付見出し *hoge*hoge 済?
定義リスト記法 :〜〜:〜〜 済み
表組み記法 |〜〜
pre記法 >| 〜〜 | 済 再帰的なのは実装してない。
superpre記法 >| 〜〜 |
作ってない。そのうち作りたい
superpre記法(ハイライト) >| ruby|〜〜 | 未 あとでキーワード一覧探す。
引用記法 >><< 未実装。再帰的なのがめんどくさい。
時間見出し *t*hoge やってない
pタグ停止記法 >< 〜〜 > 未 使わないので作ってない

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'&', '&amp;')
      str.gsub!(%r'<', '&lt;')
      str.gsub!(%r'>', '&gt;')
      str.gsub!(%r'"', '&quote;')
      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

*1:HTTP記法と、Pタグが怪しい