require File.dirname(__FILE__) + '/tag_helper' module ActionView module Helpers #:nodoc: # Provides a set of methods for working with text strings that can help unburden the level of inline Ruby code in the # templates. In the example below we iterate over a collection of posts provided to the template and print each title # after making sure it doesn't run longer than 20 characters: # <% for post in @posts %> # Title: <%= truncate(post.title, 20) %> # <% end %> module TextHelper # The regular puts and print are outlawed in eRuby. It's recommended to use the <%= "hello" %> form instead of print "hello". # If you absolutely must use a method-based output, you can use concat. It's used like this: <% concat "hello", binding %>. Notice that # it doesn't have an equal sign in front. Using <%= concat "hello" %> would result in a double hello. def concat(string, binding) eval("_erbout", binding).concat(string) end # Truncates +text+ to the length of +length+ and replaces the last three characters with the +truncate_string+ # if the +text+ is longer than +length+. def truncate(text, length = 30, truncate_string = "...") if text.nil? then return end l = length - truncate_string.length if $KCODE == "NONE" text.length > length ? text[0...l] + truncate_string : text else chars = text.split(//) chars.length > length ? chars[0...l].join + truncate_string : text end end # Highlights the +phrase+ where it is found in the +text+ by surrounding it like # I'm a highlight phrase. The highlighter can be specialized by # passing +highlighter+ as single-quoted string with \1 where the phrase is supposed to be inserted. # N.B.: The +phrase+ is sanitized to include only letters, digits, and spaces before use. def highlight(text, phrase, highlighter = '\1') if phrase.blank? then return text end text.gsub(/(#{Regexp.escape(phrase)})/i, highlighter) unless text.nil? end # Extracts an excerpt from the +text+ surrounding the +phrase+ with a number of characters on each side determined # by +radius+. If the phrase isn't found, nil is returned. Ex: # excerpt("hello my world", "my", 3) => "...lo my wo..." def excerpt(text, phrase, radius = 100, excerpt_string = "...") if text.nil? || phrase.nil? then return end phrase = Regexp.escape(phrase) if found_pos = text =~ /(#{phrase})/i start_pos = [ found_pos - radius, 0 ].max end_pos = [ found_pos + phrase.length + radius, text.length ].min prefix = start_pos > 0 ? excerpt_string : "" postfix = end_pos < text.length ? excerpt_string : "" prefix + text[start_pos..end_pos].strip + postfix else nil end end # Attempts to pluralize the +singular+ word unless +count+ is 1. See source for pluralization rules. def pluralize(count, singular, plural = nil) "#{count} " + if count == 1 singular elsif plural plural elsif Object.const_defined?("Inflector") Inflector.pluralize(singular) else singular + "s" end end # Word wrap long lines to line_width. def word_wrap(text, line_width = 80) text.gsub(/\n/, "\n\n").gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip end begin require_library_or_gem "redcloth" unless Object.const_defined?(:RedCloth) # Returns the text with all the Textile codes turned into HTML-tags. # This method is only available if RedCloth can be required. def textilize(text) if text.blank? "" else textilized = RedCloth.new(text, [ :hard_breaks ]) textilized.hard_breaks = true if textilized.respond_to?("hard_breaks=") textilized.to_html end end # Returns the text with all the Textile codes turned into HTML-tags, but without the regular bounding

tag. # This method is only available if RedCloth can be required. def textilize_without_paragraph(text) textiled = textilize(text) if textiled[0..2] == "

" then textiled = textiled[3..-1] end if textiled[-4..-1] == "

" then textiled = textiled[0..-5] end return textiled end rescue LoadError # We can't really help what's not there end begin require_library_or_gem "bluecloth" unless Object.const_defined?(:BlueCloth) # Returns the text with all the Markdown codes turned into HTML-tags. # This method is only available if BlueCloth can be required. def markdown(text) text.blank? ? "" : BlueCloth.new(text).to_html end rescue LoadError # We can't really help what's not there end # Returns +text+ transformed into HTML using very simple formatting rules # Surrounds paragraphs with

tags, and converts line breaks into
# Two consecutive newlines(\n\n) are considered as a paragraph, one newline (\n) is # considered a linebreak, three or more consecutive newlines are turned into two newlines def simple_format(text) text.gsub!(/(\r\n|\n|\r)/, "\n") # lets make them newlines crossplatform text.gsub!(/\n\n+/, "\n\n") # zap dupes text.gsub!(/\n\n/, '

\0

') # turn two newlines into paragraph text.gsub!(/([^\n])(\n)([^\n])/, '\1\2
\3') # turn single newline into
content_tag("p", text) end # Turns all urls and email addresses into clickable links. The +link+ parameter can limit what should be linked. # Options are :all (default), :email_addresses, and :urls. # # Example: # auto_link("Go to http://www.rubyonrails.com and say hello to david@loudthinking.com") => # Go to http://www.rubyonrails.com and # say hello to david@loudthinking.com # # If a block is given, each url and email address is yielded and the # result is used as the link text. Example: # auto_link(post.body, :all, :target => '_blank') do |text| # truncate(text, 15) # end def auto_link(text, link = :all, href_options = {}, &block) return '' if text.blank? case link when :all then auto_link_urls(auto_link_email_addresses(text, &block), href_options, &block) when :email_addresses then auto_link_email_addresses(text, &block) when :urls then auto_link_urls(text, href_options, &block) end end # Turns all links into words, like "else" to "else". def strip_links(text) text.gsub(/(.*)<\/a>/m, '\1') end # Try to require the html-scanner library begin require 'html/tokenizer' require 'html/node' rescue LoadError # if there isn't a copy installed, use the vendor version in # action controller $:.unshift File.join(File.dirname(__FILE__), "..", "..", "action_controller", "vendor", "html-scanner") require 'html/tokenizer' require 'html/node' end VERBOTEN_TAGS = %w(form script) unless defined?(VERBOTEN_TAGS) VERBOTEN_ATTRS = /^on/i unless defined?(VERBOTEN_ATTRS) # Sanitizes the given HTML by making form and script tags into regular # text, and removing all "onxxx" attributes (so that arbitrary Javascript # cannot be executed). Also removes href attributes that start with # "javascript:". # # Returns the sanitized text. def sanitize(html) # only do this if absolutely necessary if html.index("<") tokenizer = HTML::Tokenizer.new(html) new_text = "" while token = tokenizer.next node = HTML::Node.parse(nil, 0, 0, token, false) new_text << case node when HTML::Tag if VERBOTEN_TAGS.include?(node.name) node.to_s.gsub(/[\n]?/m, "") else html # already plain text end end # Returns a Cycle object whose to_s value cycles through items of an # array every time it is called. This can be used to alternate classes # for table rows: # # <%- for item in @items do -%> # "> # ... use item ... # # <%- end -%> # # You can use named cycles to prevent clashes in nested loops. You'll # have to reset the inner cycle, manually: # # <%- for item in @items do -%> # "row_class") # # <%- for value in item.values do -%> # "colors") %>'"> # item # # <%- end -%> # <%- reset_cycle("colors") -%> # # # <%- end -%> def cycle(first_value, *values) if (values.last.instance_of? Hash) params = values.pop name = params[:name] else name = "default" end values.unshift(first_value) cycle = get_cycle(name) if (cycle.nil? || cycle.values != values) cycle = set_cycle(name, Cycle.new(*values)) end return cycle.to_s end # Resets a cycle so that it starts from the first element in the array # the next time it is used. def reset_cycle(name = "default") cycle = get_cycle(name) return if cycle.nil? cycle.reset end class Cycle #:nodoc: attr_reader :values def initialize(first_value, *values) @values = values.unshift(first_value) reset end def reset @index = 0 end def to_s value = @values[@index].to_s @index = (@index + 1) % @values.size return value end end private # The cycle helpers need to store the cycles in a place that is # guaranteed to be reset every time a page is rendered, so it # uses an instance variable of ActionView::Base. def get_cycle(name) @_cycles = Hash.new if @_cycles.nil? return @_cycles[name] end def set_cycle(name, cycle_object) @_cycles = Hash.new if @_cycles.nil? @_cycles[name] = cycle_object end AUTO_LINK_RE = / ( # leading text <\w+.*?>| # leading HTML tag, or [^=!:'"\/]| # leading punctuation, or ^ # beginning of line ) ( (?:http[s]?:\/\/)| # protocol spec, or (?:www\.) # www.* ) ( ([\w]+:?[=?&\/.-]?)* # url segment \w+[\/]? # url tail (?:\#\w*)? # trailing anchor ) ([[:punct:]]|\s|<|$) # trailing text /x unless const_defined?(:AUTO_LINK_RE) # Turns all urls into clickable links. If a block is given, each url # is yielded and the result is used as the link text. Example: # auto_link_urls(post.body, :all, :target => '_blank') do |text| # truncate(text, 15) # end def auto_link_urls(text, href_options = {}) extra_options = tag_options(href_options.stringify_keys) || "" text.gsub(AUTO_LINK_RE) do all, a, b, c, d = $&, $1, $2, $3, $5 if a =~ /#{text}#{d}) end end end # Turns all email addresses into clickable links. If a block is given, # each email is yielded and the result is used as the link text. # Example: # auto_link_email_addresses(post.body) do |text| # truncate(text, 15) # end def auto_link_email_addresses(text) text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do text = $1 text = yield(text) if block_given? %{#{text}} end end end end end