class Gollum::Filter::TOC

Inserts header anchors and creates TOC

Constants

HEADERS

Public Instance Methods

extract(data) click to toggle source
# File lib/gollum-lib/filter/toc.rb, line 6
def extract(data)
  data
end
process(data) click to toggle source
# File lib/gollum-lib/filter/toc.rb, line 10
def process(data)

  @doc               = Nokogiri::HTML::DocumentFragment.parse(data)
  @toc_doc           = nil
  @anchor_names      = {}
  @current_ancestors = []
  @headers_levels    = {}
  toc_str            = ''
  if @markup.sub_page && @markup.parent_page
    toc_str = @markup.parent_page.toc_data
  else
    @headers = @doc.css(headers_selector)
    get_toc_levels()

    @headers.each_with_index do |header, i|
      next if header.content.empty?
      # omit the first H1 (the page title) from the TOC if so configured
      next if (i == 0 && header.name =~ /[Hh]1/) && @markup.wiki && @markup.wiki.h1_title

      anchor_name = generate_anchor_name(header)
      add_anchor_to_header header, anchor_name
      add_entry_to_toc     header, anchor_name
    end
    if not @toc_doc.nil?
      toc_str = @toc_doc.to_xml(@markup.class.to_xml_opts)
    end

    data  = @doc.to_xml(@markup.class.to_xml_opts)
  end

  @markup.toc = toc_str

  data.gsub!(/\[\[_TOC_(.*?)\]\]/) do
    levels = nil
    levels_match = Regexp.last_match[1].match /\|\s*levels\s*=\s*(\d+)/
    if levels_match
      levels = levels_match[1].to_i
    end

    if levels.nil? || toc_str.empty?
      toc_str
    else
      @toc_doc ||= Nokogiri::HTML::DocumentFragment.parse(toc_str)
      toc_clone = @toc_doc.clone
      toc_clone.traverse do |e|
        if e.name == 'ul' and e.ancestors('ul').length > levels - 1
          e.remove
        end
      end
      toc_clone.to_xml(@markup.class.to_xml_opts)
    end
  end

  data
end

Private Instance Methods

add_anchor_to_header(header, name) click to toggle source

Creates an anchor element with the given name and adds it before the given header element.

# File lib/gollum-lib/filter/toc.rb, line 127
def add_anchor_to_header(header, name)
  a = Nokogiri::XML::Node.new('a', @doc)
  a['class'] = 'anchor'
  a['id'] = name
  a['href'] = "##{name}"
  header.children.before(a) # Add anchor element before the header
end
add_entry_to_toc(header, name) click to toggle source

Adds an entry to the TOC for the given header. The generated entry is a link to the given anchor name

# File lib/gollum-lib/filter/toc.rb, line 137
def add_entry_to_toc(header, name)
  @toc_doc ||= Nokogiri::XML::DocumentFragment.parse('<div class="toc"><div class="toc-title">Table of Contents</div></div>')
  @tail ||= @toc_doc.child
  @tail_level ||= 0

  level = @headers_levels[header]

  if @tail_level < level
    while @tail_level < level
      list = Nokogiri::XML::Node.new('ul', @doc)
      @tail.add_child(list)
      @tail = list.add_child(Nokogiri::XML::Node.new('li', @doc))
      @tail_level += 1
    end
  else
    while @tail_level > level
      @tail = @tail.parent.parent
      @tail_level -= 1
    end
    @tail = @tail.parent.add_child(Nokogiri::XML::Node.new('li', @doc))
  end

  # % -> %25 so anchors work on Firefox. See issue #475
  @tail.add_child(%Q{<a href="##{name}">#{header.content}</a>})
end
generate_anchor_name(header) click to toggle source

Generates the anchor name from the given header element removing all non alphanumeric characters, replacing them with single dashes.

Generates heading ancestry prefixing the headings ancestor names to the generated name.

Prefixes duplicate anchors with an index

# File lib/gollum-lib/filter/toc.rb, line 109
def generate_anchor_name(header)
  name = header.content
  level = header.name[1..-1].to_i

  # normalize the header name
  name.gsub!(/[^\d\w\u00C0-\u1FFF\u2C00-\uD7FF]/, '-')
  name.gsub!(/-+/, '-')
  name.gsub!(/^-/, '')
  name.gsub!(/-$/, '')
  name.downcase!

  # Ensure duplicate anchors have a unique prefix or the toc will break
  index = increment_anchor_index(name)
  index.zero? ? name : "#{name}-#{index}"
end
get_header_level(header) click to toggle source
# File lib/gollum-lib/filter/toc.rb, line 72
def get_header_level(header)
  header.name.match(/[Hh]([1-6])/)[1].to_i
end
get_toc_levels() click to toggle source

Find level as it should be viewed in TOC for each header and save hash to @header_levels

# File lib/gollum-lib/filter/toc.rb, line 78
def get_toc_levels
  stack = []

  @headers.each do |header|
    cur_level = get_header_level(header)

    loop do
      prev_level = stack[-1]
      break if prev_level.nil? || prev_level < cur_level
      stack.pop
    end

    stack.push cur_level
    @headers_levels[header] = stack.size
  end
end
headers_selector() click to toggle source
# File lib/gollum-lib/filter/toc.rb, line 68
def headers_selector
  HEADERS.map { |n| make_header_tag(n) } .join(',')
end
increment_anchor_index(name) click to toggle source

Increments the number of anchors with the given name and returns the current index

# File lib/gollum-lib/filter/toc.rb, line 165
def increment_anchor_index(name)
  @anchor_names = {} if @anchor_names.nil?
  @anchor_names[name].nil? ? @anchor_names[name] = 0 : @anchor_names[name] += 1
end
make_header_tag(level) click to toggle source

Generates header in format “h<level>”

# File lib/gollum-lib/filter/toc.rb, line 96
def make_header_tag(level)
  raise "Header should be from 1 to 6" unless level.between?(1, 6)
  "h#{level}"
end