自作しようかと思ったんですが、CGI::prettyというのが
既にあるということでソースを眺めてみました。
ソース
1.8#lib/ruby/1.8/cgi.rb
1.9#lib/ruby/1.9.1/cgi/util.rb
def CGI::pretty(string, shift = " ") lines = string.gsub(/(?!\A)<(?:.|\n)*?>/n, "\n\\0").gsub(/<(?:.|\n)*?>(?!\n)/n, "\\0\n") end_pos = 0 while end_pos = lines.index(/^<\/(\w+)/n, end_pos) element = $1.dup start_pos = lines.rindex(/^\s*<#{element}/ni, end_pos) lines[start_pos ... end_pos] = "__" + lines[start_pos ... end_pos].gsub(/\n(?!\z)/n, "\n" + shift) + "__" end lines.gsub(/^((?:#{Regexp::quote(shift)})*)__(?=<\/?\w)/n, '\1') end
すごい、こんだけとは。自作するとバグ込みで3倍くらいになりそう。
第2引数に、好きなインデントを設定できるようです。
パッと見訳分かんないので、上から追ってみます。
まずはおさらい
こちらをガン見。
正規表現 - Rubyリファレンスマニュアル
(?= ) 先読み
(?! ) 否定先読み
\A 文字列先頭。^ とは異なり改行の有無には影響しません。
/n オプションのnは知らなかったけど、文字コードnoneという意味らしい。
(?: ) 項参照を伴わないグループ化
最初にまとめ
・1行目は、開始・終了タグの前後に改行を挿入。
・ループは、開始・終了タグを一塊として、前後に目印「__」を挿入し、
その塊の中にある改行の後にインデント(空白2個)を挿入することで階層化しています。
・最後に目印「__」を削除。
あぁ、ややこしぃ。
では1行目
1行目の前半
(/(?!\A)
→先頭じゃない「<」
(/(?!\A)<(?:.|\n)*?>/n
→先頭じゃない「<文字列or改行>」の塊
.gsub(/(?!\A)<(?:.|\n)*?>/n, "\n\\0")
→先頭じゃない「<文字列or改行>」の塊の前に改行を挿入
>> html="<html><head><title>test_title</title></head><body>body_sample</body></html>" >> html.gsub(/(?!\A)<(?:.|\n)*?>/n, "\n\\0") => "<html>\n<head>\n<title>test_title\n</title>\n</head>\n<body>body_sample\n</body>\n</html>"
1行目の後半
.gsub(/<(?:.|\n)*?>(?!\n)/n, "\\0\n")
→「<文字列or改行>」の塊の後ろに改行がない場合、改行(\n)を挿入
なので
1行目は開始・終了タグの前後に改行を挿入していると。
>> html.gsub(/(?!\A)<(?:.|\n)*?>/n, "\n\\0").gsub(/<(?:.|\n)*?>(?!\n)/n, "\\0\n")
=> "\n
なるほど、ここまで20分。
ループの中
while end_pos = lines.index(/^<\/(\w+)/n, end_pos)
閉じタグの位置を検索
start_pos = lines.rindex(/^\s*<#{element}/ni, end_pos)
閉じタグの位置から遡って開始タグの位置を検索
lines[start_pos ... end_pos] = "__" + lines[start_pos ... end_pos].gsub(/\n(?!\z)/n, "\n" + shift) + "__"
タグの塊の中で改行を改行 + shift(第2引数:defaultスペース2個)に置換して、前後に目印「__」をセット
#irb => "__<html>\n __<head>\n __<title>\n test_title\n __</title>\n __</head>\n __<body>\n body_sample\n __</body>\n__</html>\n"
lines.gsub(/^((?:#{Regexp::quote(shift)})*)__(?=<\/?\w)/n, '\1')
開き(閉じ)タグの前にある目印「__」を削除
#irb => "<html>\n <head>\n <title>\n test_title\n </title>\n </head>\n <body>\n body_sample\n </body>\n</html>\n"
勉強になります
いまいち目印「__」をセットする意味がよくわかってなかったりしますが、
何気なく使ってるソースを見るといろんな発見があります。
(↓これとか)
TABLE_FOR_ESCAPE_HTML__ = { '&' => '&', '"' => '"', '<' => '<', '>' => '>', } # Escape special characters in HTML, namely &\"<> # CGI::escapeHTML('Usage: foo "bar" <baz>') # # => "Usage: foo "bar" <baz>" def CGI::escapeHTML(string) string.gsub(/[&\"<>]/, TABLE_FOR_ESCAPE_HTML__) end