Yesterday's post showed how ApplicationHelper#textilizable used Redmine::WikiFormatting#to_html to convert the text content into HTML.
text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
Today I'm going to look into the #to_html method to see how the conversion is run.
The Code
module Redmine
module WikiFormatting
class << self
def to_html(format, text, options = {}, &block)
text = if Setting.cache_formatted_text? && text.size > 2.kilobyte && cache_store && cache_key = cache_key_for(format, options[:object], options[:attribute])
# Text retrieved from the cache store may be frozen
# We need to dup it so we can do in-place substitutions with gsub!
cache_store.fetch cache_key do
formatter_for(format).new(text).to_html
end.dup
else
formatter_for(format).new(text).to_html
end
if block_given?
execute_macros(text, block)
end
text
end
end
end
end
Review
The first thing that #to_html does is to check if the formatted text has been cached in ActiveSupport::Cache. Then it uses #formatter_for which will pick the configured formatter to render the content. Finally, the Redmine macros are run with #execute_macros.
Caching
text = if Setting.cache_formatted_text? && text.size > 2.kilobyte && cache_store && cache_key = cache_key_for(format, options[:object], options[:attribute])
# Text retrieved from the cache store may be frozen
# We need to dup it so we can do in-place substitutions with gsub!
cache_store.fetch cache_key do
formatter_for(format).new(text).to_html
end.dup
Caching the text formatting was a recent addition to Redmine, so it's still very strict about when content is cached.
- The Cache Formatted Text setting in the Administration panel needs to be enabled, and
- The text size needs to be bigger than 2K, and
- The cache check should miss (i.e. no preview content cached)
When all of these cases match, Redmine will run the block passed to cache_store.fetch and store the result into the cache for later use. Then Redmine uses the #formatter_for method to render the content.
Content rendering
def formatter_for(name)
entry = @@formatters[name.to_s]
(entry && entry[:formatter]) || Redmine::WikiFormatting::NullFormatter::Formatter
end
Redmine::WikiFormatting#to_html calls formatter_for(format).new(text).to_html both when caching the content and when no caching is enabled. formatter_for is just using the lookup table of valid formatters and returning their class to the caller. What's nice about this process is that Redmine is able to return the NullFormatter if nothing is found, which gives a good plain text fallback. If I assume that the NullFormatter is used, then the call for formatter_for would be converted to this:
Redmine::WikiFormatting::NullFormatter::Formatter.new(text).to_html
NullFormatter#to_html
# Default formatter module
module NullFormatter
class Formatter
include ActionView::Helpers::TagHelper
include ActionView::Helpers::TextHelper
include ActionView::Helpers::UrlHelper
def initialize(text)
@text = text
end
def to_html(*args)
simple_format(auto_link(CGI::escapeHTML(@text)))
end
end
module Helper
def wikitoolbar_for(field_id)
end
def heads_for_wiki_formatter
end
def initial_page_content(page)
page.pretty_title.to_s
end
end
end
To get a better idea of what a Formatter does, here is the entire NullFormatter::Formatter class. It's easy to see that there isn't very much going on here. First a new object is initialized with the text that needs to be rendered. Then #to_html uses simple_format and auto_link to create a basic HTML section.
Macro execution
if block_given?
execute_macros(text, block)
end
Finally Redmine::WikiFormatting#to_html runs #execute_macro when there is a block argument. I'll save reviewing #execute_macros until later, it's a complex method that uses a large regular expression to match the macros in the text.
Now that I have a better understanding of how Redmine's Wiki Formatting works, I'm going to take a deeper look at how the textile formatter is structured. Since it's using _why's RedCloth3 I'm expecting this piece to be pretty complex.
