After reading the
NullFormatter yesterday, I wanted to take a short look at how Redmine’s Textile formatter works. I’m going to try to avoid much of the parsing code as possible, it’s very complex and could use an entire week of code reading itself.
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
require 'redcloth3' module Redmine module WikiFormatting module Textile class Formatter < RedCloth3 include ActionView::Helpers::TagHelper # auto_link rule after textile rules so that it doesn't break !image_url! tags RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc] def initialize(*args) super self.hard_breaks=true self.no_span_caps=true self.filter_styles=true end def to_html(*rules) @toc =  super(*RULES).to_s end private # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet. # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a> def hard_break( text ) # ... snip ... end # Patch to add code highlighting support to RedCloth def smooth_offtags( text ) # ... snip ... end # Patch to add 'table of content' support to RedCloth def textile_p_withtoc(tag, atts, cite, content) # ... snip ... end alias :textile_h1 :textile_p_withtoc alias :textile_h2 :textile_p_withtoc alias :textile_h3 :textile_p_withtoc def inline_toc(text) # ... snip ... end # Turns all urls into clickable links (code from Rails). def inline_auto_link(text) # ... snip ... end # Turns all email addresses into clickable links (code from Rails). def inline_auto_mailto(text) # ... snip ... end end end end end
The first thing to notice is that
Textile::Formatter is a subclass of
RedCloth3. This will let the formatting reuse many of the methods from RedCloth directly. Since the formatter is used by calling
#new and then
#to_html, I’ll look at those methods first.
1 2 3 4 5 6
def initialize(*args) super self.hard_breaks=true self.no_span_caps=true self.filter_styles=true end
#initialize takes two arguments:
restrictions. Redmine only uses the first one, passing in the text content that needs to be formatted. After RedCloth has initialized the object,
Formatter sets three attributes to control the rendering:
- hard_breaks – converts single newlines to HTML break tags.
- no_span_caps – turns off the behavior where capitalized words automatically have span tags placed around them.
- filter_styles – turns off textile styles. I know this is off because there have been many security bugs found when styles were enabled
Now with a fresh object and the text content,
#to_html is used to… well, convert the text into HTML.
1 2 3 4
def to_html(*rules) @toc =  super(*RULES).to_s end
Redmine’s textile supports creating a table of contents from a wiki “macro” (1). The
@toc variable will be used later in the
#inline_toc method to render the table of contents.
Other than that,
Formatter just calls super with the formatting rules to apply:
- textile – provided by RedCloth, this runs the actual Textile conversion.
- block_markdown_rule – provided by RedCloth, this is a single rule from RedCloth’s markdown support. It converts Markdown’s horizontal rules into an
- inline_auto_link – provided by Redmine, this converts all urls into clickable links.
- inline_auto_mailto – provided by Redmine, this converts all mailto urls into clickable links.
- inline_toc – provided by Redmine, this creates the automatic table of contents from the HTML header tags (
Finally after all of the formatting is complete,
Formatter converts the content to a string and returns if back up the stack.
So these two methods are all that’s needed to hook up Redmine to RedCloth. I did some work on the Redmine markdown plugin and it also had to implement these methods to connect to a markdown library.
toc isn’t a macro though there will be a big push to port it to Redmine’s macro system.