Headshot-color me@jbrains.ca Find out where I'm appearing

Patching RedCloth: unique footnote hyperlinks

Recently, I integrated Textile into this blog via RedCloth. As I started to write articles in my Adventures in RSpec series, I found I wanted to use footnotes. Since Textile supports them, I figured I was in luck; however, I soon found a hyperlink collision when two fragments of Textile markup appear on the same page, each with the same-numbered footnote. RedCloth simply generates a hyperlink to target fn1 for footnote #1, so if there are two footnote #1s on the same page, they both link to target fn1, making it the equivalent of a race condition.

I looked for guidance to the Textpattern folks. A certain Mary told me that Textile has fixed this problem, but likely RedCloth has not. After some investigation, I agreed that this was the case. I looked through the Textile source, to the extent I can understand PHP, and saw their fix was simple: keep a table of footnote numbers to unique IDs, then use the unique ID in place of the number in the hyperlink and target. I figured that I could do that.

It took a couple of hours, and I’ll spare you the details. Here is what I wrote:

require 'digest/sha1'

class UniqueFootnoteIdGeneratingRedCloth < RedCloth
  def initialize(markup)
    @footnote_ids_by_number = {}
    super(markup)
  end

  def next_footnote_id
    # SHA1 isn't significant; I just figured it'd make a good unique ID
    Digest::SHA1.hexdigest(rand(2**64).to_s)
  end

  def to_html(*rules)
    self.scan( /\b\[([0-9]+?)\](\s)?/ ) do |match|
      @footnote_ids_by_number[$1] = next_footnote_id
    end
    
    super
  end
  
  private

  def footnote_ref(text)
    text.gsub!( /\b\[([0-9]+?)\](\s)?/ ) do |match|
      "<sup><a href=\"#fn#{@footnote_ids_by_number[$1]}\">#{$1}</a></sup>#{$2}"
    end
  end

  def textile_fn_( tag, num, atts, cite, content )
    atts << " id=\"fn#{ @footnote_ids_by_number[num] }\""
    content = "<sup>#{ num }</sup> #{ content }"
    atts = shelve( atts ) if atts
    "\t<p#{ atts }>#{ content }</p>"
  end
end

I ended up copy/pasting more code than I wanted to, so I’ll have to submit something to why for his perusal. Perhaps this will make it into a future version of RedCloth.

August 26, 2007 04:20 rails, ruby, design, adventures in RSpec
blog comments powered by Disqus