Yesterday’s post showed how ApplicationHelper#textilizable
used Redmine::WikiFormatting#to_html
to convert the text content into HTML.
1 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
module Redmine module WikiFormatting class < 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
1 2 3 4 5 6 |
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
1 2 3 4 |
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:
1 |
Redmine::WikiFormatting::NullFormatter::Formatter.new(text).to_html |
NullFormatter#to_html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# 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
1 2 3 |
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.