bogdanrada
7/2/2015 - 1:21 PM

rack_spellcheck.rb

rack_spellcheck.rb

# 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