lib/maruku/toc.rb in maruku-0.6.1 vs lib/maruku/toc.rb in maruku-0.7.0.beta1
- old
+ new
@@ -1,199 +1,188 @@
-#--
-# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
-#
-# This file is part of Maruku.
-#
-# Maruku is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# Maruku is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Maruku; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-#++
+module MaRuKu
+ # A section in the table of contents of a document.
+ class Section
+ # The depth of the section (0 for toplevel).
+ #
+ # Equivalent to `header_element.level`.
+ #
+ # @return [Fixnum]
+ attr_accessor :section_level
+ # The nested section number, e.g. `[1, 2, 5]` for Section 1.2.5.
+ #
+ # @return [Array<Fixnum>]
+ attr_accessor :section_number
-module MaRuKu
-
-class MDDocument
- # an instance of Section (see below)
- attr_accessor :toc
-end
+ # The `:header` node for this section.
+ # The value of `meta[:section]` for the header will be this node.
+ #
+ # @return [MDElement]
+ attr_accessor :header_element
- # This represents a section in the TOC.
- class Section
- # a Fixnum, is == header_element.level
- attr_accessor :section_level
-
- # An array of fixnum, like [1,2,5] for Section 1.2.5
- attr_accessor :section_number
-
- # reference to header (header has h.meta[:section] to self)
- attr_accessor :header_element
+ # The immediate child nodes of this section.
+ #
+ # @todo Why does this never contain Strings?
+ #
+ # @return [Array<MDElement>]
+ attr_accessor :immediate_children
- # Array of immediate children of this element
- attr_accessor :immediate_children
-
- # Array of Section inside this section
- attr_accessor :section_children
-
- def initialize
- @immediate_children = []
- @section_children = []
- end
- end
+ # The subsections of this section.
+ #
+ # @return [Array<Section>]
+ attr_accessor :section_children
- class Section
- def inspect(indent=1)
- s = ""
- if @header_element
- s += "\_"*indent + "(#{@section_level})>\t #{@section_number.join('.')} : "
- s += @header_element.children_to_s +
- " (id: '#{@header_element.attributes[:id]}')\n"
- else
- s += "Master\n"
- end
-
- @section_children.each do |c|
- s+=c.inspect(indent+1)
- end
- s
- end
-
- # Numerate this section and its children
- def numerate(a=[])
- self.section_number = a
- section_children.each_with_index do |c,i|
- c.numerate(a.clone.push(i+1))
- end
- if h = self.header_element
- h.attributes[:section_number] = self.section_number
- end
- end
-
- include REXML
- # Creates an HTML toc.
- # Call this on the root
- def to_html
- div = Element.new 'div'
- div.attributes['class'] = 'maruku_toc'
- div << create_toc
- div
- end
-
- def create_toc
- ul = Element.new 'ul'
- # let's remove the bullets
- ul.attributes['style'] = 'list-style: none;'
- @section_children.each do |c|
- li = Element.new 'li'
- if span = c.header_element.render_section_number
- li << span
- end
- a = c.header_element.wrap_as_element('a')
- a.delete_attribute 'id'
- a.attributes['href'] = "##{c.header_element.attributes[:id]}"
- li << a
- li << c.create_toc if c.section_children.size>0
- ul << li
- end
- ul
- end
+ def initialize
+ @immediate_children = []
+ @section_children = []
+ end
- # Creates a latex toc.
- # Call this on the root
- def to_latex
- to_latex_rec + "\n\n"
- end
-
- def to_latex_rec
- s = ""
- @section_children.each do |c|
- s += "\\noindent"
- number = c.header_element.section_number
- s += number if number
- text = c.header_element.children_to_latex
- id = c.header_element.attributes[:id]
- s += "\\hyperlink{#{id}}{#{text}}"
- s += "\\dotfill \\pageref*{#{id}} \\linebreak\n"
- s += c.to_latex_rec if c.section_children.size>0
+ def inspect(indent = 1)
+ if @header_element
+ s = "\_" * indent <<
+ "(#{@section_level})>\t #{@section_number.join('.')} : " <<
+ @header_element.children_to_s <<
+ " (id: '#{@header_element.attributes[:id]}')\n"
+ else
+ s = "Master\n"
+ end
+ @section_children.each {|c| s << c.inspect(indent + 1) }
- end
- s
- end
-
- end
+ s
+ end
- class MDDocument
-
- def create_toc
- each_element(:header) do |h|
- h.attributes[:id] ||= h.generate_id
- end
-
- stack = []
-
- # the ancestor section
- s = Section.new
- s.section_level = 0
+ # Assign \{#section\_number section numbers}
+ # to this section and its children.
+ # This also assigns the section number attribute
+ # to the sections' headers.
+ #
+ # This should only be called on the root section.
+ #
+ # @overload def numerate
+ def numerate(a = [])
+ self.section_number = a
+ self.section_children.each_with_index {|c, i| c.numerate(a + [i + 1])}
+ if h = self.header_element
+ h.attributes[:section_number] = self.section_number
+ end
+ end
- stack.push s
-
- i = 0;
- while i < @children.size
- while i < @children.size
- if @children[i].node_type == :header
- level = @children[i].level
- break if level <= stack.last.section_level+1
- end
-
- stack.last.immediate_children.push @children[i]
- i += 1
- end
- break if i>=@children.size
-
- header = @children[i]
- level = header.level
-
- if level > stack.last.section_level
- # this level is inside
-
- s2 = Section.new
- s2.section_level = level
- s2.header_element = header
- header.instance_variable_set :@section, s2
-
- stack.last.section_children.push s2
- stack.push s2
-
- i+=1
- elsif level == stack.last.section_level
- # this level is a sibling
- stack.pop
- else
- # this level is a parent
- stack.pop
- end
-
- end
+ # Returns an HTML representation of the table of contents.
+ #
+ # This should only be called on the root section.
+ def to_html
+ MaRuKu::Out::HTML::HTMLElement.new('div', { 'class' => 'maruku_toc' }, _to_html)
+ end
- # If there is only one big header, then assume
- # it is the master
- if s.section_children.size == 1
- s = s.section_children.first
- end
-
- # Assign section numbers
- s.numerate
-
- s
- end
- end
-end
\ No newline at end of file
+ # Returns a LaTeX representation of the table of contents.
+ #
+ # This should only be called on the root section.
+ def to_latex
+ _to_latex + "\n\n"
+ end
+
+ protected
+
+ def _to_html
+ ul = MaRuKu::Out::HTML::HTMLElement.new('ul')
+ @section_children.each do |c|
+ li = MaRuKu::Out::HTML::HTMLElement.new('li')
+ if span = c.header_element.render_section_number
+ li << span
+ end
+
+ a = c.header_element.wrap_as_element('a')
+ a.attributes.delete('id')
+ a['href'] = "##{c.header_element.attributes[:id]}"
+
+ li << a
+ li << c._to_html if c.section_children.size > 0
+ ul << li
+ end
+ ul
+ end
+
+ def _to_latex
+ s = ""
+ @section_children.each do |c|
+ s << "\\noindent"
+ if number = c.header_element.section_number
+ s << number
+ end
+ id = c.header_element.attributes[:id]
+ text = c.header_element.children_to_latex
+ s << "\\hyperlink{#{id}}{#{text}}"
+ s << "\\dotfill \\pageref*{#{id}} \\linebreak\n"
+ s << c._to_latex if c.section_children.size > 0
+ end
+ s
+ end
+ end
+
+ class MDDocument
+ # The table of contents for the document.
+ #
+ # @return [Section]
+ attr_accessor :toc
+
+ # A map of header IDs to a count of how many times they've occurred in the document.
+ #
+ # @return [Hash<String, Number>]
+ attr_accessor :header_ids
+
+ def create_toc
+ self.header_ids = Hash.new(0)
+
+ each_element(:header) {|h| h.attributes[:id] ||= h.generate_id }
+
+
+ # The root section
+ s = Section.new
+ s.section_level = 0
+
+ stack = [s]
+
+ i = 0
+ while i < @children.size
+ if children[i].node_type == :header
+ header = @children[i]
+ level = header.level
+ s2 = Section.new
+ s2.section_level = level
+ s2.header_element = header
+ header.instance_variable_set :@section, s2
+ while level <= stack.last.section_level
+ stack.pop
+ end
+ stack.last.section_children.push s2
+ stack.push s2
+ else
+ stack.last.immediate_children.push @children[i]
+ end
+ i += 1
+ end
+
+ # If there is only one big header, then assume
+ # it is the master
+ if s.section_children.size == 1
+ s = s.section_children.first
+ end
+
+ # Assign section numbers
+ s.numerate
+
+ s
+ end
+ end
+
+ class MDElement
+ # Generate an id for headers. Assumes @children is set.
+ def generate_id
+ raise "generate_id only makes sense for headers" unless node_type == :header
+ generated_id = children_to_s.tr(' ', '_').downcase.gsub(/\W/, '').strip
+ num_occurs = (@doc.header_ids[generated_id] += 1)
+ generated_id += "_#{num_occurs}" if num_occurs > 1
+ generated_id
+ end
+ end
+end