# $Id: outline.rb 124 2008-02-03 00:24:10Z tim_pease $
require 'hpricot'
module Webby
module Filters
# The Outline filter is used to insert outline numbering into HTML heading
# tags (h1, h2, h3, etc.) and to generate a table of contents based on the
# heading tags. The table of contents is inserted into the page at the
# location of the tag. If there is no tag, then a table of
# contents will not be created but outline numbering will still take place.
#
# If a table of contents is desired without outline number being inserted
# into the heading tags, this can be specified in the attibutes of the
# tag itself.
#
#
#
# This will generate a table of contents, but not insert outline numbering
# into the heading tags.
#
# The Outline filter will only work on valid HTML or XHTML pages. Therefore
# it should be used after any markup langauge filters (textile, markdown,
# etc.).
#
class Outline
class Error < StandardError; end # :nodoc:
# call-seq:
# Outline.new( html )
#
# Creates a new outline filter that will operate on the given
# _html_ string.
#
def initialize( str )
@str = str
@cur_level, @base_level, @cur_depth = nil
@level = [0] * 6
@h_rgxp = %r/^h(\d)$/o
@toc = []
@outline_numbering = true
end
# call-seq:
# filter => html
#
# Process the original html document passed to the filter when it was
# created. The document will be scanned for heading tags (h1, h2, etc.)
# and outline numbering and id attributes will be inserted. A table of
# contents will also be created and inserted into the page if a
# tag is found.
#
# For example, if there is a heading tag
#
#
Get Fuzzy
#
# somewhere in a page about comic strips, the tag might be altered as such
#
# 'heading-num')}
end
elem['id'] = "h#{lbl.tr('.','_')}" if elem['id'].nil?
return [text, elem['id']]
end
# Set the current heading level. This will set the label and depth as
# well. An error will be raised if the _level_ is less than the base
# heading level.
#
# The base heading level will be set to the _level_ if it has not already
# been set. Therefore, the first heading tag encountered defines the base
# heading level.
#
def current_level=( level )
@base_level ||= level
@cur_level ||= level
if level < @base_level
raise Error, "heading tags are not in order, cannot outline"
end
if level == @cur_level
@level[level-1] += 1
elsif level > @cur_level
@cur_level.upto(level-1) {|ii| @level[ii] += 1}
else
@cur_level.downto(level+1) {|ii| @level[ii-1] = 0}
@level[level-1] += 1
end
@cur_level = level
end
# Return the label string for the current heading level.
#
def label
rv = @level.dup
rv.delete(0)
rv.join('.')
end
# Return the nesting depth of the current heading level with respect to the
# base heading level. This is a one-based number.
#
def depth
@cur_level - @base_level + 1
end
# Add the given text and id reference to the table of contents.
#
def add_to_toc( text, id )
str = '#' * depth
str << ' ' << "\"#{text}\":##{id}"
@toc << str
end
# Returns the table of contents as a collection of nested ordered lists.
# This is fully formatted HTML.
#
def toc
RedCloth.new(@toc.join("\n"), %w(no_span_caps)).to_html
end
# Returns +true+ if outline numbering should be inserted into the heading
# tags. Returns +false+ otherwise.
#
def outline_numbering?
@outline_numbering
end
end # class Outline
# Generate a outline numbering and/or a table of contents in the input HTML
# text.
#
register :outline do |input|
Outline.new(input).filter
end
end # module Filters
end # module Webby
# EOF