Daily Code Reading #33 – Redmine Textile Formatting

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.

The Code

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

Review

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.

initialize

1
2
3
4
5
6
def initialize(*args)
  super
  self.hard_breaks=true
  self.no_span_caps=true
  self.filter_styles=true
end

RedCloth’s #initialize takes two arguments: string and 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.

to_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 <hr> element.
  • 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 (h1, h2, h3).

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.

(1) Technically toc isn’t a macro though there will be a big push to port it to Redmine’s macro system.