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['id'] || node.text.parameterize.presence || 'sec'
 | |
|       @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
 |