69 lines
		
	
	
	
		
			1.5 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			69 lines
		
	
	
	
		
			1.5 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
# frozen_string_literal: true
 | 
						|
 | 
						|
class TOCGenerator
 | 
						|
  TARGET_ELEMENTS = %w(h1 h2 h3 h4 h5 h6).freeze
 | 
						|
  LISTED_ELEMENTS = %w(h2 h3).freeze
 | 
						|
 | 
						|
  class Section
 | 
						|
    attr_accessor :depth, :title, :children, :anchor
 | 
						|
 | 
						|
    def initialize(depth, title, anchor)
 | 
						|
      @depth    = depth
 | 
						|
      @title    = title
 | 
						|
      @children = []
 | 
						|
      @anchor   = anchor
 | 
						|
    end
 | 
						|
 | 
						|
    delegate :<<, to: :children
 | 
						|
  end
 | 
						|
 | 
						|
  def initialize(source_html)
 | 
						|
    @source_html = source_html
 | 
						|
    @processed   = false
 | 
						|
    @target_html = ''
 | 
						|
    @headers     = []
 | 
						|
    @slugs       = Hash.new { |h, k| h[k] = 0 }
 | 
						|
  end
 | 
						|
 | 
						|
  def html
 | 
						|
    parse_and_transform unless @processed
 | 
						|
    @target_html
 | 
						|
  end
 | 
						|
 | 
						|
  def toc
 | 
						|
    parse_and_transform unless @processed
 | 
						|
    @headers
 | 
						|
  end
 | 
						|
 | 
						|
  private
 | 
						|
 | 
						|
  def parse_and_transform
 | 
						|
    return if @source_html.blank?
 | 
						|
 | 
						|
    parsed_html = Nokogiri::HTML.fragment(@source_html)
 | 
						|
 | 
						|
    parsed_html.traverse do |node|
 | 
						|
      next unless TARGET_ELEMENTS.include?(node.name)
 | 
						|
 | 
						|
      anchor = node.text.parameterize
 | 
						|
      @slugs[anchor] += 1
 | 
						|
      anchor = "#{anchor}-#{@slugs[anchor]}" if @slugs[anchor] > 1
 | 
						|
 | 
						|
      node['id'] = anchor
 | 
						|
 | 
						|
      next unless LISTED_ELEMENTS.include?(node.name)
 | 
						|
 | 
						|
      depth          = node.name[1..-1]
 | 
						|
      latest_section = @headers.last
 | 
						|
 | 
						|
      if latest_section.nil? || latest_section.depth >= depth
 | 
						|
        @headers << Section.new(depth, node.text, anchor)
 | 
						|
      else
 | 
						|
        latest_section << Section.new(depth, node.text, anchor)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    @target_html = parsed_html.to_s
 | 
						|
    @processed   = true
 | 
						|
  end
 | 
						|
end
 |