argius note

プログラミング関連

簡易HTMLパーサ

RubyにHTMLパーサは数有れど、どれが標準なのか良く分からない。調べるより作ったほうが楽そうなので、ちょっと作ってみました。

# htmlparser.rb
class Tag
  attr_reader :token, :type, :name, :attr, :text
  def initialize token
    @token = token
    if token.match /^<!DOCTYPE(.+)>$/m
      @type = "D"
      @text = $1
    elsif token.match /^<!--(.+)-->$/m
      @type = "C"
      @text = $1
    elsif token.match /^<\/([^\s]+) *.*>$/m
      @type = "E"
      @name = $1.downcase
    elsif token.match /^<([^\s]+) *(.*)>$/m
      @type = "S"
      @name = $1.downcase
      @attr = {}
      @order = []
      $2.split.each do |attr|
        if attr.match /([^=]+)="?([^'"]+)"?/
          key = $1
          value = $2
        else
          key = attr
          value = nil
        end
        @attr[key.downcase] = value
        @order << key
      end
    else
      @type = "T"
      @text = token
    end
  end
  def to_s
    @token
  end
end
    
class Parser
  def initialize src
    @src = src
    @pos = 0
  end
  def next
    if @src[@pos, 4] == "<!--"
      target = "-->"
      adjust = 2
    elsif @src[@pos, 1] == "<"
      target = ">"
      adjust = 0
    else
      target = "<"
      adjust = -1
    end
    from = @pos
    to = @src.index target, from
    unless to
      @pos = @src.length
      return
    end
    to += adjust
    @pos = to + 1
    Tag.new @src[from..to]
  end
end

これは、PerlのHTML::TokeParserを表面的に模倣しています。使い方は次のような感じです。

require 'net/http'
require 'htmlparser.rb'

url = ""  # URL
path = "" # PATH

Net::HTTP.version_1_2
Net::HTTP.start(url, 80) do |http|
  p = Parser.new http.get(path).body
  while tag = p.next
    p tag
  end
end

簡単なHTMLなら大体使えると思いますが、今思いつく範囲では、以下の点を改善したほうが良いでしょう。

  • scriptタグに対応する(head内でコメントになっていない場合など)
  • HTMLソースをストリームのまま解析できるようにする