# If you'd like this packaged up as a gem, send me a note. You can get
# in touch with me at http://www.techiferous.com/about
require 'nokogiri'
require 'ispell'
module Rack
class SpellCheck
def initialize(app, options = {})
@app = app
@options = options
# The way ispell parses words means that it's hard to skip URLs.
# Ignoring these "words" eases the pain somewhat.
@ignore = ["www", "com", "http", "org", "html"]
if options[:ignore].is_a? Array
@ignore += options[:ignore].map(&:downcase)
end
end
def call(env)
@doc = nil
@request = Rack::Request.new(env)
status, @headers, @body = @app.call(env)
if html?
find_spelling_errors
highlight_spelling_errors
update_content_length
end
[status, @headers, @body]
end
private
def find_spelling_errors
@speller = Ispell.new('ispell', 'english') # note: we are forking a process
doc.at_css("body").traverse do |node|
if node.text?
node.content = spellcheck(node.content)
end
end
@speller.destroy! # stop the process
@body = doc.to_html
end
def highlight_spelling_errors
style = "background-color: yellow; color: red; font-weight: bold;"
@body.gsub!('changethistobeginningtaglater', "<span style=\"#{style}\">")
@body.gsub!('changethistoendingtaglater', '</span>')
end
def spellcheck(text)
results = @speller.spellcheck(text)
new_text = text
results.each do |res|
case res.type
when :miss, :guess, :none
unless @ignore.include?(res.original.downcase)
new_text.gsub!(res.original,
"changethistobeginningtaglater#{res.original}changethistoendingtaglater")
end
end
end
new_text
end
def html?
@headers["Content-Type"] && @headers["Content-Type"].include?("text/html")
end
def doc
@doc ||= Nokogiri::HTML(body_to_string)
end
def body_to_string
s = ""
@body.each { |x| s << x }
s
end
def update_content_length
@headers['Content-Length'] = Rack::Utils.bytesize(@body).to_s
end
end
end