RubyでHTMLを自動整形する

HTMLと言うかXMLの自動インデント。1行でべたっと書かれたXMLに、かしこく改行とインデントを付ける。StringScannerが便利。

require 'strscan'

src = %[<html><body><h1>chapter</h1><div><dl><dt>hoge</dt><dl>fuga</dl></dl></div><p>pi<img src="piyo.png"/>yo</p></body></html>]

no_indent_tag = %w[html head body]   # インデントしない要素
indent_str = '  '   # インデントに使う文字(半角スペース2つ)

s = StringScanner.new(src)

slash = '/'.unpack('C')[0]
indent = 0
body = ''
while len = s.exist?(/<\/?([^\ >]+)[^>]*>/)
  if s.matched[1] != slash
    # 開きタグ
    if len == s.matched_size   
      body << "\n"
      body << indent_str * indent
    else
      body << s.peek(len - s.matched_size)
    end
    body << s.matched
    unless s.matched[-2] == slash ||   # 空要素
              no_indent_tag.include?(s[1])   # インデントしない要素
      indent += 1
    end
  else
    # 閉じタグ
    unless no_indent_tag.include?(s[1]) || indent == 0
      indent -= 1
    end
    if len == s.matched_size
      body << "\n"
      body << indent_str * indent
    else
      body << s.peek(len - s.matched_size)
    end
    body << s.matched
  end
  s.pos += len
end
body << s.rest
body.lstrip!    # 先頭に改行が入ってしまうので削除する

puts body

↓実行結果

<html>
<body>
<h1>chapter</h1>
<div>
  <dl>
    <dt>hoge</dt>
    <dl>fuga</dl>
  </dl>
</div>
<p>pi<img src="piyo.png"/>yo</p>
</body>
</html>

テキストの中にあるタグ(≒インライン要素)の前後や、子要素がテキストだけの要素には改行が入らないのがポイント。